@hydranium/data-client-vscode 1.0.0-next.322 → 1.0.0-next.324
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 +19 -1
- package/lib/node/index.d.ts +1 -0
- package/lib/node/index.d.ts.map +1 -1
- package/lib/node/index.js +1 -0
- package/lib/node/index.js.map +1 -1
- package/lib/node/socket-data-port.d.ts +54 -0
- package/lib/node/socket-data-port.d.ts.map +1 -0
- package/lib/node/socket-data-port.js +96 -0
- package/lib/node/socket-data-port.js.map +1 -0
- package/package.json +3 -3
- package/src/node/index.ts +1 -0
- package/src/node/socket-data-port.ts +109 -0
package/README.md
CHANGED
|
@@ -16,6 +16,8 @@ to it over a `vscode-messenger` hop, and the webview's data port reads it.
|
|
|
16
16
|
- The webview's `DataPort`, for the connection and sessions from
|
|
17
17
|
`@hydranium/protocol`, which rejects pending requests when the extension host
|
|
18
18
|
reports the relay lost.
|
|
19
|
+
- A `DataPort` over a socket for code in the extension host itself, such as a
|
|
20
|
+
command, which looks the port up again for every connection.
|
|
19
21
|
|
|
20
22
|
## Install
|
|
21
23
|
|
|
@@ -78,6 +80,22 @@ messenger.start();
|
|
|
78
80
|
const connection = new DataConnectionWithEvents(port);
|
|
79
81
|
```
|
|
80
82
|
|
|
83
|
+
For code in the extension host itself, give the connection a `SocketDataPort`
|
|
84
|
+
instead, which opens the socket there; nothing connects until the first
|
|
85
|
+
request:
|
|
86
|
+
|
|
87
|
+
<!-- snippet-preamble
|
|
88
|
+
declare function findDataServerPort(): Promise<number>;
|
|
89
|
+
-->
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { SocketDataPort } from '@hydranium/data-client-vscode/node';
|
|
93
|
+
import { DataConnectionWithEvents } from '@hydranium/protocol';
|
|
94
|
+
|
|
95
|
+
const port = new SocketDataPort({ findPort: findDataServerPort });
|
|
96
|
+
const connection = new DataConnectionWithEvents(port);
|
|
97
|
+
```
|
|
98
|
+
|
|
81
99
|
To test a webview's data path in Node, `makeMessengerHarness` from `./testing`
|
|
82
100
|
joins the real extension-side and webview-side messengers through a fake
|
|
83
101
|
panel and a fake `window`, so the test runs their real routing.
|
|
@@ -90,7 +108,7 @@ For the whole setup, see *Host in VS Code* in
|
|
|
90
108
|
| Subpath | Use it for | Runs in |
|
|
91
109
|
| ----------- | --------------------------------------------- | -------------------------- |
|
|
92
110
|
| `.` | The messenger channel both ends build on | anywhere |
|
|
93
|
-
| `./node` | The relay
|
|
111
|
+
| `./node` | The relay, and the extension host's own port | the VS Code extension host |
|
|
94
112
|
| `./browser` | The webview's data port | a webview |
|
|
95
113
|
| `./testing` | The real messenger pair joined in a Node test | a test |
|
|
96
114
|
|
package/lib/node/index.d.ts
CHANGED
package/lib/node/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/node/index.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,cAAc,0BAA0B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/node/index.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,cAAc,0BAA0B,CAAC;AACzC,cAAc,oBAAoB,CAAC"}
|
package/lib/node/index.js
CHANGED
|
@@ -23,4 +23,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
23
23
|
};
|
|
24
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
25
|
__exportStar(require("./data-server-connection"), exports);
|
|
26
|
+
__exportStar(require("./socket-data-port"), exports);
|
|
26
27
|
//# sourceMappingURL=index.js.map
|
package/lib/node/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/node/index.ts"],"names":[],"mappings":";AAAA;;;;;;;kFAOkF;;;;;;;;;;;;;;;;AAElF,2DAAyC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/node/index.ts"],"names":[],"mappings":";AAAA;;;;;;;kFAOkF;;;;;;;;;;;;;;;;AAElF,2DAAyC;AACzC,qDAAmC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (c) 2026 EclipseSource and others.
|
|
3
|
+
*
|
|
4
|
+
* This program and the accompanying materials are made available under the
|
|
5
|
+
* terms of the MIT License which is available in the project root.
|
|
6
|
+
*
|
|
7
|
+
* SPDX-License-Identifier: MIT
|
|
8
|
+
********************************************************************************/
|
|
9
|
+
import { type DataPort, type Logger, type ResolvedMessage } from '@hydranium/protocol';
|
|
10
|
+
import { Emitter } from 'vscode-jsonrpc';
|
|
11
|
+
import { type MessageConnection } from 'vscode-jsonrpc/node';
|
|
12
|
+
/** What {@link SocketDataPort} needs from the host. */
|
|
13
|
+
export interface SocketDataPortOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Resolve the data-server's listening port. Called once per connection
|
|
16
|
+
* generation, so a restarted server is re-discovered rather than cached.
|
|
17
|
+
*/
|
|
18
|
+
readonly findPort: () => Promise<number>;
|
|
19
|
+
/** The data-server's host. Defaults to the loopback address. */
|
|
20
|
+
readonly host?: string;
|
|
21
|
+
/** Where the connection logs its protocol faults, and what it does on its own. */
|
|
22
|
+
readonly logger?: Logger;
|
|
23
|
+
/** Surface a failure to the user. Defaults to the logger, else the console. */
|
|
24
|
+
readonly reportError?: (error: unknown, reported: ResolvedMessage) => void;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A {@link DataPort} over a socket to the data-server, for code in the
|
|
28
|
+
* extension host itself, such as a command. A webview has no `net`; it takes
|
|
29
|
+
* `WebviewDataPort` over `connectDataServer` instead.
|
|
30
|
+
*
|
|
31
|
+
* Each generation opens its own socket and closes the one before, without
|
|
32
|
+
* firing {@link onDispose} for it; of two overlapping calls, the one that
|
|
33
|
+
* settles last keeps its socket. A socket the server ends under the current
|
|
34
|
+
* generation fires it, so `DataConnection` reconnects to the port
|
|
35
|
+
* {@link SocketDataPortOptions.findPort} finds next.
|
|
36
|
+
*/
|
|
37
|
+
export declare class SocketDataPort implements DataPort {
|
|
38
|
+
protected readonly options: SocketDataPortOptions;
|
|
39
|
+
protected readonly disposeEmitter: Emitter<void>;
|
|
40
|
+
readonly onDispose: import("vscode-jsonrpc").Event<void>;
|
|
41
|
+
readonly logger?: Logger;
|
|
42
|
+
protected disposed: boolean;
|
|
43
|
+
protected current?: {
|
|
44
|
+
readonly connection: MessageConnection;
|
|
45
|
+
readonly dispose: () => void;
|
|
46
|
+
};
|
|
47
|
+
constructor(options: SocketDataPortOptions);
|
|
48
|
+
connect(): Promise<MessageConnection>;
|
|
49
|
+
reportError(error: unknown, reported: ResolvedMessage): void;
|
|
50
|
+
/** The consumer hears it first; then the current socket closes. */
|
|
51
|
+
dispose(): void;
|
|
52
|
+
protected closeCurrent(): void;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=socket-data-port.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"socket-data-port.d.ts","sourceRoot":"","sources":["../../src/node/socket-data-port.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EAA0B,KAAK,QAAQ,EAAE,KAAK,MAAM,EAAE,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC/G,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AACzC,OAAO,EAA2B,KAAK,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAGtF,uDAAuD;AACvD,MAAM,WAAW,qBAAqB;IACnC;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACzC,gEAAgE;IAChE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,kFAAkF;IAClF,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,+EAA+E;IAC/E,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,eAAe,KAAK,IAAI,CAAC;CAC7E;AAED;;;;;;;;;;GAUG;AACH,qBAAa,cAAe,YAAW,QAAQ;IAOhC,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,qBAAqB;IAN7D,SAAS,CAAC,QAAQ,CAAC,cAAc,gBAAuB;IACxD,QAAQ,CAAC,SAAS,uCAA6B;IAC/C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,QAAQ,UAAS;IAC3B,SAAS,CAAC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,UAAU,EAAE,iBAAiB,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAA;KAAE,CAAC;IAE7F,YAA+B,OAAO,EAAE,qBAAqB,EAE5D;IAEK,OAAO,IAAI,OAAO,CAAC,iBAAiB,CAAC,CA6B1C;IAED,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,eAAe,GAAG,IAAI,CAQ3D;IAED,mEAAmE;IACnE,OAAO,IAAI,IAAI,CAQd;IAED,SAAS,CAAC,YAAY,IAAI,IAAI,CAI7B;CACH"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/********************************************************************************
|
|
3
|
+
* Copyright (c) 2026 EclipseSource and others.
|
|
4
|
+
*
|
|
5
|
+
* This program and the accompanying materials are made available under the
|
|
6
|
+
* terms of the MIT License which is available in the project root.
|
|
7
|
+
*
|
|
8
|
+
* SPDX-License-Identifier: MIT
|
|
9
|
+
********************************************************************************/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.SocketDataPort = void 0;
|
|
12
|
+
const protocol_1 = require("@hydranium/protocol");
|
|
13
|
+
const vscode_jsonrpc_1 = require("vscode-jsonrpc");
|
|
14
|
+
const node_1 = require("vscode-jsonrpc/node");
|
|
15
|
+
const data_server_connection_1 = require("./data-server-connection");
|
|
16
|
+
/**
|
|
17
|
+
* A {@link DataPort} over a socket to the data-server, for code in the
|
|
18
|
+
* extension host itself, such as a command. A webview has no `net`; it takes
|
|
19
|
+
* `WebviewDataPort` over `connectDataServer` instead.
|
|
20
|
+
*
|
|
21
|
+
* Each generation opens its own socket and closes the one before, without
|
|
22
|
+
* firing {@link onDispose} for it; of two overlapping calls, the one that
|
|
23
|
+
* settles last keeps its socket. A socket the server ends under the current
|
|
24
|
+
* generation fires it, so `DataConnection` reconnects to the port
|
|
25
|
+
* {@link SocketDataPortOptions.findPort} finds next.
|
|
26
|
+
*/
|
|
27
|
+
class SocketDataPort {
|
|
28
|
+
options;
|
|
29
|
+
disposeEmitter = new vscode_jsonrpc_1.Emitter();
|
|
30
|
+
onDispose = this.disposeEmitter.event;
|
|
31
|
+
logger;
|
|
32
|
+
disposed = false;
|
|
33
|
+
current;
|
|
34
|
+
constructor(options) {
|
|
35
|
+
this.options = options;
|
|
36
|
+
this.logger = options.logger;
|
|
37
|
+
}
|
|
38
|
+
async connect() {
|
|
39
|
+
if (this.disposed) {
|
|
40
|
+
throw new Error('SocketDataPort: the port is disposed');
|
|
41
|
+
}
|
|
42
|
+
const transport = await (0, data_server_connection_1.openSocketTransport)(await this.options.findPort(), this.options.host);
|
|
43
|
+
// Disposed while the socket opened: nothing would close this one.
|
|
44
|
+
if (this.disposed) {
|
|
45
|
+
transport.dispose?.();
|
|
46
|
+
throw new Error('SocketDataPort: the port is disposed');
|
|
47
|
+
}
|
|
48
|
+
// The one before, or an overlapping call's that was installed meanwhile.
|
|
49
|
+
this.closeCurrent();
|
|
50
|
+
const connection = (0, node_1.createMessageConnection)(transport.reader, transport.writer, this.logger);
|
|
51
|
+
const current = {
|
|
52
|
+
connection,
|
|
53
|
+
dispose: () => {
|
|
54
|
+
connection.dispose();
|
|
55
|
+
transport.dispose?.();
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
this.current = current;
|
|
59
|
+
connection.onClose(() => {
|
|
60
|
+
if (this.current === current) {
|
|
61
|
+
this.closeCurrent();
|
|
62
|
+
this.disposeEmitter.fire();
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
connection.listen();
|
|
66
|
+
return connection;
|
|
67
|
+
}
|
|
68
|
+
reportError(error, reported) {
|
|
69
|
+
if (this.options.reportError) {
|
|
70
|
+
this.options.reportError(error, reported);
|
|
71
|
+
}
|
|
72
|
+
else if (this.logger) {
|
|
73
|
+
this.logger.error((0, protocol_1.renderFrameworkMessage)(reported), error);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
console.error(`[data head] ${(0, protocol_1.renderFrameworkMessage)(reported)}`, error);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** The consumer hears it first; then the current socket closes. */
|
|
80
|
+
dispose() {
|
|
81
|
+
if (this.disposed) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
this.disposed = true;
|
|
85
|
+
this.disposeEmitter.fire();
|
|
86
|
+
this.disposeEmitter.dispose();
|
|
87
|
+
this.closeCurrent();
|
|
88
|
+
}
|
|
89
|
+
closeCurrent() {
|
|
90
|
+
const current = this.current;
|
|
91
|
+
this.current = undefined;
|
|
92
|
+
current?.dispose();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
exports.SocketDataPort = SocketDataPort;
|
|
96
|
+
//# sourceMappingURL=socket-data-port.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"socket-data-port.js","sourceRoot":"","sources":["../../src/node/socket-data-port.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hydranium/data-client-vscode",
|
|
3
|
-
"version": "1.0.0-next.
|
|
3
|
+
"version": "1.0.0-next.324",
|
|
4
4
|
"description": "VS Code client primitives for the hydranium data-server protocol head; companion of @hydranium/data-server. The extension host relays the data-server's socket to a webview over a vscode-messenger hop, and the webview's data port reads it, so DataConnection and DataSession run unchanged in a webview.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"hydranium",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"watch": "tsc -b -w --preserveWatchOutput"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
|
-
"@hydranium/protocol": "1.0.0-next.
|
|
66
|
+
"@hydranium/protocol": "1.0.0-next.324",
|
|
67
67
|
"@types/node": "^22.0.0",
|
|
68
68
|
"@types/vscode": "^1.101.0",
|
|
69
69
|
"rimraf": "^5.0.0",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"vscode-messenger-webview": "^0.4.5"
|
|
75
75
|
},
|
|
76
76
|
"peerDependencies": {
|
|
77
|
-
"@hydranium/protocol": "1.0.0-next.
|
|
77
|
+
"@hydranium/protocol": "1.0.0-next.324",
|
|
78
78
|
"vscode-jsonrpc": "^9.0.0",
|
|
79
79
|
"vscode-messenger": "^0.4.5",
|
|
80
80
|
"vscode-messenger-common": "^0.4.5",
|
package/src/node/index.ts
CHANGED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (c) 2026 EclipseSource and others.
|
|
3
|
+
*
|
|
4
|
+
* This program and the accompanying materials are made available under the
|
|
5
|
+
* terms of the MIT License which is available in the project root.
|
|
6
|
+
*
|
|
7
|
+
* SPDX-License-Identifier: MIT
|
|
8
|
+
********************************************************************************/
|
|
9
|
+
|
|
10
|
+
import { renderFrameworkMessage, type DataPort, type Logger, type ResolvedMessage } from '@hydranium/protocol';
|
|
11
|
+
import { Emitter } from 'vscode-jsonrpc';
|
|
12
|
+
import { createMessageConnection, type MessageConnection } from 'vscode-jsonrpc/node';
|
|
13
|
+
import { openSocketTransport } from './data-server-connection';
|
|
14
|
+
|
|
15
|
+
/** What {@link SocketDataPort} needs from the host. */
|
|
16
|
+
export interface SocketDataPortOptions {
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the data-server's listening port. Called once per connection
|
|
19
|
+
* generation, so a restarted server is re-discovered rather than cached.
|
|
20
|
+
*/
|
|
21
|
+
readonly findPort: () => Promise<number>;
|
|
22
|
+
/** The data-server's host. Defaults to the loopback address. */
|
|
23
|
+
readonly host?: string;
|
|
24
|
+
/** Where the connection logs its protocol faults, and what it does on its own. */
|
|
25
|
+
readonly logger?: Logger;
|
|
26
|
+
/** Surface a failure to the user. Defaults to the logger, else the console. */
|
|
27
|
+
readonly reportError?: (error: unknown, reported: ResolvedMessage) => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A {@link DataPort} over a socket to the data-server, for code in the
|
|
32
|
+
* extension host itself, such as a command. A webview has no `net`; it takes
|
|
33
|
+
* `WebviewDataPort` over `connectDataServer` instead.
|
|
34
|
+
*
|
|
35
|
+
* Each generation opens its own socket and closes the one before, without
|
|
36
|
+
* firing {@link onDispose} for it; of two overlapping calls, the one that
|
|
37
|
+
* settles last keeps its socket. A socket the server ends under the current
|
|
38
|
+
* generation fires it, so `DataConnection` reconnects to the port
|
|
39
|
+
* {@link SocketDataPortOptions.findPort} finds next.
|
|
40
|
+
*/
|
|
41
|
+
export class SocketDataPort implements DataPort {
|
|
42
|
+
protected readonly disposeEmitter = new Emitter<void>();
|
|
43
|
+
readonly onDispose = this.disposeEmitter.event;
|
|
44
|
+
readonly logger?: Logger;
|
|
45
|
+
protected disposed = false;
|
|
46
|
+
protected current?: { readonly connection: MessageConnection; readonly dispose: () => void };
|
|
47
|
+
|
|
48
|
+
constructor(protected readonly options: SocketDataPortOptions) {
|
|
49
|
+
this.logger = options.logger;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async connect(): Promise<MessageConnection> {
|
|
53
|
+
if (this.disposed) {
|
|
54
|
+
throw new Error('SocketDataPort: the port is disposed');
|
|
55
|
+
}
|
|
56
|
+
const transport = await openSocketTransport(await this.options.findPort(), this.options.host);
|
|
57
|
+
// Disposed while the socket opened: nothing would close this one.
|
|
58
|
+
if (this.disposed) {
|
|
59
|
+
transport.dispose?.();
|
|
60
|
+
throw new Error('SocketDataPort: the port is disposed');
|
|
61
|
+
}
|
|
62
|
+
// The one before, or an overlapping call's that was installed meanwhile.
|
|
63
|
+
this.closeCurrent();
|
|
64
|
+
const connection = createMessageConnection(transport.reader, transport.writer, this.logger);
|
|
65
|
+
const current = {
|
|
66
|
+
connection,
|
|
67
|
+
dispose: () => {
|
|
68
|
+
connection.dispose();
|
|
69
|
+
transport.dispose?.();
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
this.current = current;
|
|
73
|
+
connection.onClose(() => {
|
|
74
|
+
if (this.current === current) {
|
|
75
|
+
this.closeCurrent();
|
|
76
|
+
this.disposeEmitter.fire();
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
connection.listen();
|
|
80
|
+
return connection;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
reportError(error: unknown, reported: ResolvedMessage): void {
|
|
84
|
+
if (this.options.reportError) {
|
|
85
|
+
this.options.reportError(error, reported);
|
|
86
|
+
} else if (this.logger) {
|
|
87
|
+
this.logger.error(renderFrameworkMessage(reported), error);
|
|
88
|
+
} else {
|
|
89
|
+
console.error(`[data head] ${renderFrameworkMessage(reported)}`, error);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** The consumer hears it first; then the current socket closes. */
|
|
94
|
+
dispose(): void {
|
|
95
|
+
if (this.disposed) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
this.disposed = true;
|
|
99
|
+
this.disposeEmitter.fire();
|
|
100
|
+
this.disposeEmitter.dispose();
|
|
101
|
+
this.closeCurrent();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
protected closeCurrent(): void {
|
|
105
|
+
const current = this.current;
|
|
106
|
+
this.current = undefined;
|
|
107
|
+
current?.dispose();
|
|
108
|
+
}
|
|
109
|
+
}
|