@hydranium/data-client-vscode 0.0.0-stage → 1.0.0-next
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/LICENSE +21 -0
- package/README.md +110 -2
- package/lib/browser/index.d.ts +10 -0
- package/lib/browser/index.d.ts.map +1 -0
- package/lib/browser/index.js +26 -0
- package/lib/browser/index.js.map +1 -0
- package/lib/browser/webview-data-port.d.ts +64 -0
- package/lib/browser/webview-data-port.d.ts.map +1 -0
- package/lib/browser/webview-data-port.js +97 -0
- package/lib/browser/webview-data-port.js.map +1 -0
- package/lib/index.d.ts +10 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +29 -0
- package/lib/index.js.map +1 -0
- package/lib/messenger-channel.d.ts +155 -0
- package/lib/messenger-channel.d.ts.map +1 -0
- package/lib/messenger-channel.js +106 -0
- package/lib/messenger-channel.js.map +1 -0
- package/lib/node/data-server-connection.d.ts +57 -0
- package/lib/node/data-server-connection.d.ts.map +1 -0
- package/lib/node/data-server-connection.js +92 -0
- package/lib/node/data-server-connection.js.map +1 -0
- package/lib/node/index.d.ts +10 -0
- package/lib/node/index.d.ts.map +1 -0
- package/lib/node/index.js +26 -0
- package/lib/node/index.js.map +1 -0
- package/lib/testing/index.d.ts +10 -0
- package/lib/testing/index.d.ts.map +1 -0
- package/lib/testing/index.js +26 -0
- package/lib/testing/index.js.map +1 -0
- package/lib/testing/messenger-harness.d.ts +48 -0
- package/lib/testing/messenger-harness.d.ts.map +1 -0
- package/lib/testing/messenger-harness.js +91 -0
- package/lib/testing/messenger-harness.js.map +1 -0
- package/package.json +99 -4
- package/src/browser/index.ts +10 -0
- package/src/browser/webview-data-port.ts +115 -0
- package/src/index.ts +13 -0
- package/src/messenger-channel.ts +224 -0
- package/src/node/data-server-connection.ts +85 -0
- package/src/node/index.ts +10 -0
- package/src/testing/index.ts +10 -0
- package/src/testing/messenger-harness.ts +121 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 CrossBreeze, EclipseSource and others.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,111 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@hydranium/data-client-vscode`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The data head's client side in a VS Code extension built on
|
|
4
|
+
[Hydranium](https://github.com/eclipse-emfcloud/hydranium). Install it when a
|
|
5
|
+
webview of your extension reads or writes models through the data head: a
|
|
6
|
+
webview has no sockets, so the extension host relays the data-server's socket
|
|
7
|
+
to it over a `vscode-messenger` hop, and the webview's data port reads it.
|
|
8
|
+
|
|
9
|
+
## What it gives you
|
|
10
|
+
|
|
11
|
+
- The extension host's relay from the data-server's socket to one webview,
|
|
12
|
+
with the port looked up again for every connection, so a restarted server
|
|
13
|
+
is found.
|
|
14
|
+
- A channel over the messenger for each end of the hop, filtered to the one
|
|
15
|
+
webview it serves, so the hop can be shared with GLSP's diagram webviews.
|
|
16
|
+
- The webview's `DataPort`, for the connection and sessions from
|
|
17
|
+
`@hydranium/protocol`, which rejects pending requests when the extension host
|
|
18
|
+
reports the relay lost.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install @hydranium/data-client-vscode
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
| Peer | Range |
|
|
27
|
+
| -------------------------- | ------------- |
|
|
28
|
+
| `@hydranium/protocol` | `^1.0.0-next` |
|
|
29
|
+
| `vscode-jsonrpc` | `^9.0.0` |
|
|
30
|
+
| `vscode-messenger` | `^0.4.5` |
|
|
31
|
+
| `vscode-messenger-common` | `^0.4.5` |
|
|
32
|
+
| `vscode-messenger-webview` | `^0.4.5` |
|
|
33
|
+
|
|
34
|
+
The `vscode-messenger` packages are optional; only `./testing` loads them.
|
|
35
|
+
Your extension and webview use the copies GLSP's VS Code integration brings,
|
|
36
|
+
or their own.
|
|
37
|
+
|
|
38
|
+
## Wiring
|
|
39
|
+
|
|
40
|
+
In the extension host, connect each webview to the data head over a messenger
|
|
41
|
+
constructed with `ignoreHiddenViews: false`, as `GlspVscodeConnector`'s is, or
|
|
42
|
+
a hidden webview misses every answer. Tell the webview when the relay closes,
|
|
43
|
+
since nothing else reaches it:
|
|
44
|
+
|
|
45
|
+
<!-- snippet-preamble
|
|
46
|
+
import type { MessageParticipant } from 'vscode-messenger-common';
|
|
47
|
+
import type { Messenger } from 'vscode-messenger';
|
|
48
|
+
declare const messenger: Messenger;
|
|
49
|
+
declare const webview: MessageParticipant;
|
|
50
|
+
declare function findDataServerPort(): Promise<number>;
|
|
51
|
+
declare function tellWebviewConnectionLost(): void;
|
|
52
|
+
-->
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import { connectDataServer } from '@hydranium/data-client-vscode/node';
|
|
56
|
+
|
|
57
|
+
const relay = connectDataServer({ messenger, webview, findPort: findDataServerPort });
|
|
58
|
+
relay.onClose(() => tellWebviewConnectionLost());
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
In the webview, build the port over the mirror channel, start the messenger,
|
|
62
|
+
and hand the port to a data connection; call `connectionLost` on the port
|
|
63
|
+
when the extension host says the relay closed:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import { createWebviewSideChannel } from '@hydranium/data-client-vscode';
|
|
67
|
+
import { WebviewDataPort } from '@hydranium/data-client-vscode/browser';
|
|
68
|
+
import { DataConnectionWithEvents } from '@hydranium/protocol';
|
|
69
|
+
import { HOST_EXTENSION } from 'vscode-messenger-common';
|
|
70
|
+
import { Messenger, type VsCodeApi } from 'vscode-messenger-webview';
|
|
71
|
+
|
|
72
|
+
// VS Code injects it into the webview; there is nothing to import.
|
|
73
|
+
declare function acquireVsCodeApi(): VsCodeApi;
|
|
74
|
+
|
|
75
|
+
const messenger = new Messenger(acquireVsCodeApi());
|
|
76
|
+
const port = new WebviewDataPort(createWebviewSideChannel(messenger, HOST_EXTENSION));
|
|
77
|
+
messenger.start();
|
|
78
|
+
const connection = new DataConnectionWithEvents(port);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
To test a webview's data path in Node, `makeMessengerHarness` from `./testing`
|
|
82
|
+
joins the real extension-side and webview-side messengers through a fake
|
|
83
|
+
panel and a fake `window`, so the test runs their real routing.
|
|
84
|
+
|
|
85
|
+
For the whole setup, see *Host in VS Code* in
|
|
86
|
+
[Adopting Hydranium](https://github.com/eclipse-emfcloud/hydranium/blob/main/docs/ADOPTING.md).
|
|
87
|
+
|
|
88
|
+
## Entry points
|
|
89
|
+
|
|
90
|
+
| Subpath | Use it for | Runs in |
|
|
91
|
+
| ----------- | --------------------------------------------- | -------------------------- |
|
|
92
|
+
| `.` | The messenger channel both ends build on | anywhere |
|
|
93
|
+
| `./node` | The relay from the data-server's socket | the VS Code extension host |
|
|
94
|
+
| `./browser` | The webview's data port | a webview |
|
|
95
|
+
| `./testing` | The real messenger pair joined in a Node test | a test |
|
|
96
|
+
|
|
97
|
+
The subpaths need a TypeScript `moduleResolution` that reads `exports`
|
|
98
|
+
(`NodeNext` or `Bundler`); see *Requirements* in
|
|
99
|
+
[Adopting Hydranium](https://github.com/eclipse-emfcloud/hydranium/blob/main/docs/ADOPTING.md).
|
|
100
|
+
|
|
101
|
+
## Status
|
|
102
|
+
|
|
103
|
+
Alpha: every release is a prerelease that may break the API, so pin an exact
|
|
104
|
+
version. Guides and known limitations:
|
|
105
|
+
[Adopting Hydranium](https://github.com/eclipse-emfcloud/hydranium/blob/main/docs/ADOPTING.md).
|
|
106
|
+
|
|
107
|
+
## License
|
|
108
|
+
|
|
109
|
+
`MIT` — see this package's [`LICENSE`](./LICENSE), and the repository
|
|
110
|
+
[`NOTICE.md`](https://github.com/eclipse-emfcloud/hydranium/blob/main/NOTICE.md)
|
|
111
|
+
for third-party notices.
|
|
@@ -0,0 +1,10 @@
|
|
|
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
|
+
export * from './webview-data-port';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/browser/index.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,cAAc,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
22
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
23
|
+
};
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
__exportStar(require("./webview-data-port"), exports);
|
|
26
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/browser/index.ts"],"names":[],"mappings":";AAAA;;;;;;;kFAOkF;;;;;;;;;;;;;;;;AAElF,sDAAoC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
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 PostMessageChannel, type ResolvedMessage } from '@hydranium/protocol';
|
|
10
|
+
import { Emitter, type Event, type MessageConnection } from 'vscode-jsonrpc';
|
|
11
|
+
/** Options for {@link WebviewDataPort}. */
|
|
12
|
+
export interface WebviewDataPortOptions {
|
|
13
|
+
/**
|
|
14
|
+
* Surface a data-head failure. Without one it goes to the webview's console;
|
|
15
|
+
* a webview whose extension shows notifications forwards it there instead.
|
|
16
|
+
*/
|
|
17
|
+
readonly reportError?: (error: unknown, reported: ResolvedMessage) => void;
|
|
18
|
+
/** Where the connection logs its protocol faults, and what it does on its own. */
|
|
19
|
+
readonly logger?: Logger;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The webview's {@link DataPort}: the sandbox side of the data head, over the
|
|
23
|
+
* extension host's messenger hop.
|
|
24
|
+
*
|
|
25
|
+
* `vscode-jsonrpc/browser`, not the package root, which installs no runtime
|
|
26
|
+
* abstraction layer and throws on the first message rather than at wire-up.
|
|
27
|
+
*/
|
|
28
|
+
export declare class WebviewDataPort implements DataPort {
|
|
29
|
+
protected readonly channel: PostMessageChannel;
|
|
30
|
+
protected readonly options: WebviewDataPortOptions;
|
|
31
|
+
protected readonly disposeEmitter: Emitter<void>;
|
|
32
|
+
readonly onDispose: Event<void>;
|
|
33
|
+
readonly logger?: Logger;
|
|
34
|
+
protected disposed: boolean;
|
|
35
|
+
/** The connection every generation gets until it is disposed. */
|
|
36
|
+
protected connection?: MessageConnection;
|
|
37
|
+
constructor(channel: PostMessageChannel, options?: WebviewDataPortOptions);
|
|
38
|
+
/**
|
|
39
|
+
* Hand back a listening connection over the hop: the same one until it is
|
|
40
|
+
* disposed, and then a fresh transport, so a disposed connection leaves no
|
|
41
|
+
* second reader on the pipe.
|
|
42
|
+
*
|
|
43
|
+
* Every connection reaches the one relay behind the pipe, and each numbers
|
|
44
|
+
* its requests from zero, so a second live one would take answers meant for
|
|
45
|
+
* the first. A retry after a failed readiness check therefore gets the
|
|
46
|
+
* connection the check used.
|
|
47
|
+
*/
|
|
48
|
+
connect(): Promise<MessageConnection>;
|
|
49
|
+
reportError(error: unknown, reported: ResolvedMessage): void;
|
|
50
|
+
/**
|
|
51
|
+
* The extension host reports the relay's framed side died.
|
|
52
|
+
*
|
|
53
|
+
* A `PostMessageChannel` has no `close()`, so the pipe still looks open from
|
|
54
|
+
* here: firing `onDispose` makes `DataConnection` drop its generation, and
|
|
55
|
+
* disposing the connection rejects everything in flight, also when no
|
|
56
|
+
* generation holds it. The connection is forgotten before the event, so a
|
|
57
|
+
* listener that connects gets a fresh one rather than the one about to be
|
|
58
|
+
* disposed.
|
|
59
|
+
*/
|
|
60
|
+
connectionLost(): void;
|
|
61
|
+
/** Refuses to connect afterwards: the consumer's reconnect would otherwise open a reader nothing releases. */
|
|
62
|
+
dispose(): void;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=webview-data-port.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webview-data-port.d.ts","sourceRoot":"","sources":["../../src/browser/webview-data-port.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EAGJ,KAAK,QAAQ,EACb,KAAK,MAAM,EACX,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACtB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAG7E,2CAA2C;AAC3C,MAAM,WAAW,sBAAsB;IACpC;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,eAAe,KAAK,IAAI,CAAC;IAC3E,kFAAkF;IAClF,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;GAMG;AACH,qBAAa,eAAgB,YAAW,QAAQ;IAS1C,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,kBAAkB;IAC9C,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,sBAAsB;IATrD,SAAS,CAAC,QAAQ,CAAC,cAAc,gBAAuB;IACxD,QAAQ,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,CAA6B;IAC5D,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,QAAQ,UAAS;IAC3B,iEAAiE;IACjE,SAAS,CAAC,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAEzC,YACsB,OAAO,EAAE,kBAAkB,EAC3B,OAAO,GAAE,sBAA2B,EAGzD;IAED;;;;;;;;;OASG;IACG,OAAO,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAiB1C;IAED,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,eAAe,GAAG,IAAI,CAM3D;IAED;;;;;;;;;OASG;IACH,cAAc,IAAI,IAAI,CAKrB;IAED,8GAA8G;IAC9G,OAAO,IAAI,IAAI,CAKd;CACH"}
|
|
@@ -0,0 +1,97 @@
|
|
|
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.WebviewDataPort = void 0;
|
|
12
|
+
const protocol_1 = require("@hydranium/protocol");
|
|
13
|
+
const vscode_jsonrpc_1 = require("vscode-jsonrpc");
|
|
14
|
+
const browser_1 = require("vscode-jsonrpc/browser");
|
|
15
|
+
/**
|
|
16
|
+
* The webview's {@link DataPort}: the sandbox side of the data head, over the
|
|
17
|
+
* extension host's messenger hop.
|
|
18
|
+
*
|
|
19
|
+
* `vscode-jsonrpc/browser`, not the package root, which installs no runtime
|
|
20
|
+
* abstraction layer and throws on the first message rather than at wire-up.
|
|
21
|
+
*/
|
|
22
|
+
class WebviewDataPort {
|
|
23
|
+
channel;
|
|
24
|
+
options;
|
|
25
|
+
disposeEmitter = new vscode_jsonrpc_1.Emitter();
|
|
26
|
+
onDispose = this.disposeEmitter.event;
|
|
27
|
+
logger;
|
|
28
|
+
disposed = false;
|
|
29
|
+
/** The connection every generation gets until it is disposed. */
|
|
30
|
+
connection;
|
|
31
|
+
constructor(channel, options = {}) {
|
|
32
|
+
this.channel = channel;
|
|
33
|
+
this.options = options;
|
|
34
|
+
this.logger = options.logger;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Hand back a listening connection over the hop: the same one until it is
|
|
38
|
+
* disposed, and then a fresh transport, so a disposed connection leaves no
|
|
39
|
+
* second reader on the pipe.
|
|
40
|
+
*
|
|
41
|
+
* Every connection reaches the one relay behind the pipe, and each numbers
|
|
42
|
+
* its requests from zero, so a second live one would take answers meant for
|
|
43
|
+
* the first. A retry after a failed readiness check therefore gets the
|
|
44
|
+
* connection the check used.
|
|
45
|
+
*/
|
|
46
|
+
async connect() {
|
|
47
|
+
if (this.disposed) {
|
|
48
|
+
throw new Error('WebviewDataPort: the port is disposed');
|
|
49
|
+
}
|
|
50
|
+
if (!this.connection) {
|
|
51
|
+
const transport = (0, protocol_1.createPostMessageTransport)(this.channel);
|
|
52
|
+
const connection = (0, browser_1.createMessageConnection)(transport.reader, transport.writer, this.logger);
|
|
53
|
+
connection.onDispose(() => {
|
|
54
|
+
transport.dispose();
|
|
55
|
+
if (this.connection === connection) {
|
|
56
|
+
this.connection = undefined;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
connection.listen();
|
|
60
|
+
this.connection = connection;
|
|
61
|
+
}
|
|
62
|
+
return this.connection;
|
|
63
|
+
}
|
|
64
|
+
reportError(error, reported) {
|
|
65
|
+
if (this.options.reportError) {
|
|
66
|
+
this.options.reportError(error, reported);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
console.error(`[data head] ${(0, protocol_1.renderFrameworkMessage)(reported)}`, error);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The extension host reports the relay's framed side died.
|
|
74
|
+
*
|
|
75
|
+
* A `PostMessageChannel` has no `close()`, so the pipe still looks open from
|
|
76
|
+
* here: firing `onDispose` makes `DataConnection` drop its generation, and
|
|
77
|
+
* disposing the connection rejects everything in flight, also when no
|
|
78
|
+
* generation holds it. The connection is forgotten before the event, so a
|
|
79
|
+
* listener that connects gets a fresh one rather than the one about to be
|
|
80
|
+
* disposed.
|
|
81
|
+
*/
|
|
82
|
+
connectionLost() {
|
|
83
|
+
const lost = this.connection;
|
|
84
|
+
this.connection = undefined;
|
|
85
|
+
this.disposeEmitter.fire();
|
|
86
|
+
lost?.dispose();
|
|
87
|
+
}
|
|
88
|
+
/** Refuses to connect afterwards: the consumer's reconnect would otherwise open a reader nothing releases. */
|
|
89
|
+
dispose() {
|
|
90
|
+
this.disposed = true;
|
|
91
|
+
this.disposeEmitter.fire();
|
|
92
|
+
this.disposeEmitter.dispose();
|
|
93
|
+
this.connection?.dispose();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
exports.WebviewDataPort = WebviewDataPort;
|
|
97
|
+
//# sourceMappingURL=webview-data-port.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webview-data-port.js","sourceRoot":"","sources":["../../src/browser/webview-data-port.ts"],"names":[],"mappings":";AAAA;;;;;;;kFAOkF;;;AAElF,kDAO6B;AAC7B,mDAA6E;AAC7E,oDAAiE;AAajE;;;;;;GAMG;AACH;IASyB,OAAO;IACP,OAAO;IATV,cAAc,GAAG,IAAI,wBAAO,EAAQ,CAAC;IAC/C,SAAS,GAAgB,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC;IACnD,MAAM,CAAU;IACf,QAAQ,GAAG,KAAK,CAAC;IAC3B,iEAAiE;IACvD,UAAU,CAAqB;IAEzC,YACsB,OAA2B,EAC3B,OAAO,GAA2B,EAAE;uBADpC,OAAO;uBACP,OAAO;QAE1B,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAChC,CAAC;IAED;;;;;;;;;OASG;IACH,KAAK,CAAC,OAAO;QACV,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;QAC5D,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,MAAM,SAAS,GAAG,IAAA,qCAA0B,EAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC3D,MAAM,UAAU,GAAG,IAAA,iCAAuB,EAAC,SAAS,CAAC,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;YAC5F,UAAU,CAAC,SAAS,CAAC,GAAG,EAAE;gBACvB,SAAS,CAAC,OAAO,EAAE,CAAC;gBACpB,IAAI,IAAI,CAAC,UAAU,KAAK,UAAU,EAAE,CAAC;oBAClC,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;gBAC/B,CAAC;YACJ,CAAC,CAAC,CAAC;YACH,UAAU,CAAC,MAAM,EAAE,CAAC;YACpB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAChC,CAAC;QACD,OAAO,IAAI,CAAC,UAAU,CAAC;IAC1B,CAAC;IAED,WAAW,CAAC,KAAc,EAAE,QAAyB;QAClD,IAAI,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;YAC5B,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;QAC7C,CAAC;aAAM,CAAC;YACL,OAAO,CAAC,KAAK,CAAC,eAAe,IAAA,iCAAsB,EAAC,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;QAC3E,CAAC;IACJ,CAAC;IAED;;;;;;;;;OASG;IACH,cAAc;QACX,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC;QAC7B,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;QAC5B,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;QAC3B,IAAI,EAAE,OAAO,EAAE,CAAC;IACnB,CAAC;IAED,8GAA8G;IAC9G,OAAO;QACJ,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;QAC3B,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,CAAC;QAC9B,IAAI,CAAC,UAAU,EAAE,OAAO,EAAE,CAAC;IAC9B,CAAC;CACH"}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
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
|
+
export * from './messenger-channel';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAKlF,cAAc,qBAAqB,CAAC"}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
22
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
23
|
+
};
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
// Neutral: the messenger channel both ends of the hop build on, free of
|
|
26
|
+
// `vscode`, Node and the DOM. The extension host's end is `./node`, the
|
|
27
|
+
// webview's `./browser`.
|
|
28
|
+
__exportStar(require("./messenger-channel"), exports);
|
|
29
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;kFAOkF;;;;;;;;;;;;;;;;AAElF,wEAAwE;AACxE,wEAAwE;AACxE,yBAAyB;AACzB,sDAAoC"}
|
|
@@ -0,0 +1,155 @@
|
|
|
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 { PostMessageChannel } from '@hydranium/protocol';
|
|
10
|
+
import type { Disposable, Message } from 'vscode-jsonrpc';
|
|
11
|
+
/**
|
|
12
|
+
* A notification type as `vscode-messenger` defines it: a method name, with the
|
|
13
|
+
* payload type carried only in the generic parameter.
|
|
14
|
+
*
|
|
15
|
+
* Restated rather than imported: `vscode-messenger-common`'s
|
|
16
|
+
* `NotificationType<P>` is `{ method: string }` with `P` phantom, and the import
|
|
17
|
+
* would pin the adopter's messenger copy, which GLSP's VS Code integration
|
|
18
|
+
* chooses.
|
|
19
|
+
*/
|
|
20
|
+
export interface NotificationTypeLike {
|
|
21
|
+
readonly method: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The one method both ends of the hop share: hand a message to a participant.
|
|
25
|
+
*
|
|
26
|
+
* `TParticipant` stays generic because the two ends address different things,
|
|
27
|
+
* the extension side a registered webview and the webview side
|
|
28
|
+
* `HOST_EXTENSION`.
|
|
29
|
+
*/
|
|
30
|
+
export interface MessengerSendLike<TParticipant> {
|
|
31
|
+
sendNotification(type: NotificationTypeLike, receiver: TParticipant, params?: Message): void;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The two methods the channel needs from the **extension-side** messenger.
|
|
35
|
+
*
|
|
36
|
+
* Two methods rather than the class, so any messenger with them fits, GLSP's
|
|
37
|
+
* included, without pinning a `vscode-messenger` copy.
|
|
38
|
+
*/
|
|
39
|
+
export interface MessengerLike<TParticipant> extends MessengerSendLike<TParticipant> {
|
|
40
|
+
onNotification(type: NotificationTypeLike, handler: (params: Message, sender: TParticipant) => void, options?: {
|
|
41
|
+
sender?: TParticipant;
|
|
42
|
+
}): Disposable;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The **webview-side** messenger, which differs from the extension side in
|
|
46
|
+
* every point the channel depends on:
|
|
47
|
+
*
|
|
48
|
+
* - **`onNotification` returns the messenger itself**, not a `Disposable`, and
|
|
49
|
+
* there is no unregister API, so a registration lasts as long as the webview.
|
|
50
|
+
* {@link createMessengerChannel} therefore stops delivering through a flag.
|
|
51
|
+
* - **There is no `sender` option**: a webview has one peer.
|
|
52
|
+
* - **`start()` must be called** before anything arrives, since it attaches the
|
|
53
|
+
* `window` listener. It is absent here because the channel does not call it;
|
|
54
|
+
* the webview's bootstrap does.
|
|
55
|
+
*
|
|
56
|
+
* A separate interface rather than a wider {@link MessengerLike}: one shape for
|
|
57
|
+
* both ends would promise a webview an unregisterable handler and a sender
|
|
58
|
+
* filter it cannot honour.
|
|
59
|
+
*/
|
|
60
|
+
export interface WebviewMessengerLike<TParticipant> extends MessengerSendLike<TParticipant> {
|
|
61
|
+
onNotification(type: NotificationTypeLike, handler: (params: Message, sender: TParticipant) => void): unknown;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The data head's two notification types, one per direction.
|
|
65
|
+
*
|
|
66
|
+
* `vscode-messenger` addresses a notification to its receiver, so one method
|
|
67
|
+
* for both directions would also deliver correctly. Two keep the direction
|
|
68
|
+
* readable on the wire and in a messenger trace, and keep a delivery that
|
|
69
|
+
* routes by method alone, such as `BROADCAST`, from reaching the sender's own
|
|
70
|
+
* handler.
|
|
71
|
+
*/
|
|
72
|
+
export declare const DATA_TO_WEBVIEW: NotificationTypeLike;
|
|
73
|
+
export declare const DATA_TO_EXTENSION: NotificationTypeLike;
|
|
74
|
+
/**
|
|
75
|
+
* Options for {@link createMessengerChannel}.
|
|
76
|
+
*
|
|
77
|
+
* Generic over the registration type as well as the participant, because the two
|
|
78
|
+
* sides disagree on what `onNotification` hands back: a `Disposable` on the
|
|
79
|
+
* extension side, the messenger itself on the webview side. The type parameter
|
|
80
|
+
* keeps {@link releaseRegistration} typed; probing an `unknown` for a `dispose`
|
|
81
|
+
* method would dispose the whole webview messenger if it ever gained one.
|
|
82
|
+
*/
|
|
83
|
+
export interface MessengerChannelOptions<TParticipant, TRegistration> {
|
|
84
|
+
readonly messenger: {
|
|
85
|
+
sendNotification(type: NotificationTypeLike, receiver: TParticipant, params?: Message): void;
|
|
86
|
+
onNotification(type: NotificationTypeLike, handler: (params: Message, sender: TParticipant) => void, options?: {
|
|
87
|
+
sender?: TParticipant;
|
|
88
|
+
}): TRegistration;
|
|
89
|
+
};
|
|
90
|
+
/** Where this side's outbound messages are addressed. */
|
|
91
|
+
readonly receiver: TParticipant;
|
|
92
|
+
/** The notification type this side SENDS on. */
|
|
93
|
+
readonly outbound: NotificationTypeLike;
|
|
94
|
+
/** The notification type this side LISTENS on. */
|
|
95
|
+
readonly inbound: NotificationTypeLike;
|
|
96
|
+
/**
|
|
97
|
+
* Accept inbound notifications only from this participant. Supply it
|
|
98
|
+
* whenever the messenger is shared, as GLSP's is with every diagram
|
|
99
|
+
* webview, so a second webview cannot inject traffic into this connection.
|
|
100
|
+
*
|
|
101
|
+
* Extension side only: the webview-side messenger ignores it.
|
|
102
|
+
*/
|
|
103
|
+
readonly sender?: TParticipant;
|
|
104
|
+
/**
|
|
105
|
+
* Observe the hop going away, the webview being disposed. Passed through to
|
|
106
|
+
* {@link PostMessageChannel.onClose}, whose reader and writer use it to fire
|
|
107
|
+
* their own close.
|
|
108
|
+
*/
|
|
109
|
+
readonly onClose?: (listener: () => void) => Disposable;
|
|
110
|
+
/**
|
|
111
|
+
* Release what `onNotification` returned, where the messenger hands back
|
|
112
|
+
* something releasable: on the extension side, whose registrations would
|
|
113
|
+
* otherwise accumulate on a hop that outlives every webview on it.
|
|
114
|
+
*/
|
|
115
|
+
readonly releaseRegistration?: (registration: TRegistration) => void;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Present a messenger as a {@link PostMessageChannel}, so the data head's
|
|
119
|
+
* transport rides a host's existing notification hop.
|
|
120
|
+
*
|
|
121
|
+
* `createPostMessageTransport` turns the channel into a reader and writer,
|
|
122
|
+
* `relayToPostMessageChannel` pumps the extension host's socket onto it from
|
|
123
|
+
* the other side, and everything above, `createRpcProxy` and `DataSession`
|
|
124
|
+
* included, is host-invariant. Sharing GLSP's `Messenger` rather than opening a
|
|
125
|
+
* second hop gives a diagram and a data webview one lifecycle and one dispose.
|
|
126
|
+
*
|
|
127
|
+
* **`onMessage`'s `Disposable` means "stop delivering", not necessarily
|
|
128
|
+
* "unregister".** The webview-side messenger cannot unregister, so a flag keeps
|
|
129
|
+
* the promise on both sides. Where unregistering is possible,
|
|
130
|
+
* {@link MessengerChannelOptions.releaseRegistration} does it too, or every
|
|
131
|
+
* disposed webview would leave a handler on a shared hop.
|
|
132
|
+
*/
|
|
133
|
+
export declare function createMessengerChannel<TParticipant, TRegistration>(options: MessengerChannelOptions<TParticipant, TRegistration>): PostMessageChannel;
|
|
134
|
+
/**
|
|
135
|
+
* The extension host's end: posts toward the webview, listens for what the
|
|
136
|
+
* webview sends.
|
|
137
|
+
*/
|
|
138
|
+
export declare function createExtensionSideChannel<TParticipant>(messenger: MessengerLike<TParticipant>, webview: TParticipant, onClose?: (listener: () => void) => Disposable): PostMessageChannel;
|
|
139
|
+
/**
|
|
140
|
+
* The webview's end: the mirror image, addressed at the extension host.
|
|
141
|
+
*
|
|
142
|
+
* Two preconditions the webview's bootstrap owns, both of which present as "no
|
|
143
|
+
* data ever arrives" when missed:
|
|
144
|
+
*
|
|
145
|
+
* - **Read the channel through `WebviewDataPort`**, or build its connection
|
|
146
|
+
* with `createMessageConnection` from `vscode-jsonrpc/browser`: the package
|
|
147
|
+
* root installs no runtime abstraction layer and throws on the first
|
|
148
|
+
* message.
|
|
149
|
+
* - **Call the messenger's own `start()`**, which attaches the `window`
|
|
150
|
+
* listener.
|
|
151
|
+
*
|
|
152
|
+
* No `sender` filter: a webview has one peer.
|
|
153
|
+
*/
|
|
154
|
+
export declare function createWebviewSideChannel<TParticipant>(messenger: WebviewMessengerLike<TParticipant>, hostExtension: TParticipant, onClose?: (listener: () => void) => Disposable): PostMessageChannel;
|
|
155
|
+
//# sourceMappingURL=messenger-channel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messenger-channel.d.ts","sourceRoot":"","sources":["../src/messenger-channel.ts"],"names":[],"mappings":"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"}
|