@hydranium/data-client-theia 1.0.0-next.10
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 +126 -0
- package/lib/browser/channel-connection.d.ts +174 -0
- package/lib/browser/channel-connection.d.ts.map +1 -0
- package/lib/browser/channel-connection.js +264 -0
- package/lib/browser/channel-connection.js.map +1 -0
- package/lib/browser/data-service-frontend.d.ts +154 -0
- package/lib/browser/data-service-frontend.d.ts.map +1 -0
- package/lib/browser/data-service-frontend.js +169 -0
- package/lib/browser/data-service-frontend.js.map +1 -0
- package/lib/browser/diagnostics-data-service-frontend.d.ts +35 -0
- package/lib/browser/diagnostics-data-service-frontend.d.ts.map +1 -0
- package/lib/browser/diagnostics-data-service-frontend.js +54 -0
- package/lib/browser/diagnostics-data-service-frontend.js.map +1 -0
- package/lib/browser/host-diagnostics-frontend.d.ts +19 -0
- package/lib/browser/host-diagnostics-frontend.d.ts.map +1 -0
- package/lib/browser/host-diagnostics-frontend.js +31 -0
- package/lib/browser/host-diagnostics-frontend.js.map +1 -0
- package/lib/browser/index.d.ts +15 -0
- package/lib/browser/index.d.ts.map +1 -0
- package/lib/browser/index.js +31 -0
- package/lib/browser/index.js.map +1 -0
- package/lib/browser/references-data-service-frontend.d.ts +45 -0
- package/lib/browser/references-data-service-frontend.d.ts.map +1 -0
- package/lib/browser/references-data-service-frontend.js +55 -0
- package/lib/browser/references-data-service-frontend.js.map +1 -0
- package/lib/browser/workspace-gate.d.ts +21 -0
- package/lib/browser/workspace-gate.d.ts.map +1 -0
- package/lib/browser/workspace-gate.js +36 -0
- package/lib/browser/workspace-gate.js.map +1 -0
- package/lib/common/emitter-data-client.d.ts +40 -0
- package/lib/common/emitter-data-client.d.ts.map +1 -0
- package/lib/common/emitter-data-client.js +49 -0
- package/lib/common/emitter-data-client.js.map +1 -0
- package/lib/common/index.d.ts +10 -0
- package/lib/common/index.d.ts.map +1 -0
- package/lib/common/index.js +37 -0
- package/lib/common/index.js.map +1 -0
- package/lib/index.d.ts +10 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +11 -0
- package/lib/index.js.map +1 -0
- package/lib/node/connection-container-module.d.ts +34 -0
- package/lib/node/connection-container-module.d.ts.map +1 -0
- package/lib/node/connection-container-module.js +53 -0
- package/lib/node/connection-container-module.js.map +1 -0
- package/lib/node/data-server-connection-handler.d.ts +56 -0
- package/lib/node/data-server-connection-handler.d.ts.map +1 -0
- package/lib/node/data-server-connection-handler.js +63 -0
- package/lib/node/data-server-connection-handler.js.map +1 -0
- package/lib/node/host-diagnostics-server.d.ts +31 -0
- package/lib/node/host-diagnostics-server.d.ts.map +1 -0
- package/lib/node/host-diagnostics-server.js +62 -0
- package/lib/node/host-diagnostics-server.js.map +1 -0
- package/lib/node/index.d.ts +13 -0
- package/lib/node/index.d.ts.map +1 -0
- package/lib/node/index.js +29 -0
- package/lib/node/index.js.map +1 -0
- package/lib/node/socket-channel-forwarder.d.ts +34 -0
- package/lib/node/socket-channel-forwarder.d.ts.map +1 -0
- package/lib/node/socket-channel-forwarder.js +63 -0
- package/lib/node/socket-channel-forwarder.js.map +1 -0
- package/package.json +101 -0
- package/src/browser/channel-connection.ts +413 -0
- package/src/browser/data-service-frontend.ts +208 -0
- package/src/browser/diagnostics-data-service-frontend.ts +70 -0
- package/src/browser/host-diagnostics-frontend.ts +30 -0
- package/src/browser/index.ts +15 -0
- package/src/browser/references-data-service-frontend.ts +70 -0
- package/src/browser/workspace-gate.ts +35 -0
- package/src/common/emitter-data-client.ts +64 -0
- package/src/common/index.ts +21 -0
- package/src/index.ts +18 -0
- package/src/node/connection-container-module.ts +51 -0
- package/src/node/data-server-connection-handler.ts +76 -0
- package/src/node/host-diagnostics-server.ts +57 -0
- package/src/node/index.ts +13 -0
- package/src/node/socket-channel-forwarder.ts +64 -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
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# `@hydranium/data-client-theia`
|
|
2
|
+
|
|
3
|
+
Theia client primitives for the hydranium **data head** — the companion of
|
|
4
|
+
`@hydranium/data-server` in the
|
|
5
|
+
[Hydranium](https://github.com/eclipse-emfcloud/hydranium) framework.
|
|
6
|
+
|
|
7
|
+
The data head's frontend speaks the data server's own `vscode-jsonrpc` protocol
|
|
8
|
+
over a channel that the Theia backend relays byte-for-byte onto the server's
|
|
9
|
+
socket. This package is both halves of that arrangement: the browser-side
|
|
10
|
+
connection and typed-frontend bases, and the backend-side connection handler and
|
|
11
|
+
forwarder. Install it if a Theia application needs form editors, trees, or
|
|
12
|
+
code-gen driven from the live AST rather than from LSP text edits.
|
|
13
|
+
|
|
14
|
+
## What it gives you
|
|
15
|
+
|
|
16
|
+
- **`openChannelConnection`** with `createChannelConnection` and
|
|
17
|
+
`ChannelConnectionHandle` — wraps a Theia `Channel` as a `vscode-jsonrpc`
|
|
18
|
+
`MessageConnection`, gated on a `whenReady` promise, and by default
|
|
19
|
+
re-establishes it by **re-opening the channel** when the connection is lost.
|
|
20
|
+
Re-opening rather than rebuilding is what recovers a restarted language server:
|
|
21
|
+
a server restart closes only the multiplexed sub-channel, so no replacement
|
|
22
|
+
channel ever arrives on its own. Retries follow `DEFAULT_RECONNECT_DELAYS`,
|
|
23
|
+
escalating per consecutive loss and resetting after
|
|
24
|
+
`RECONNECT_ESCALATION_RESET_MS`.
|
|
25
|
+
- **`AbstractDataServiceFrontend`** — base for a frontend that owns the
|
|
26
|
+
data-server connection: the workspace-gated connect, the typed server proxy,
|
|
27
|
+
the inbound client binding, and a lazy idempotent init gate (`ensureConnected`).
|
|
28
|
+
You supply the connection seams (`connectionProvider`, `servicePath`,
|
|
29
|
+
`methodNamespace`, `client`, `clientMethods`); progress UI and domain caching
|
|
30
|
+
layer on top.
|
|
31
|
+
- **`AbstractDiagnosticsDataServiceFrontend`** and
|
|
32
|
+
**`AbstractReferencesDataServiceFrontend`** — the same base specialised for the
|
|
33
|
+
two protocol fragments an adopter most often keeps: each diagnostics
|
|
34
|
+
(`dumpServerState`, `writeHeapSnapshot`, `dumpPodMemory`, the profiling and
|
|
35
|
+
latency calls) and each reference method is implemented as a readiness-gated
|
|
36
|
+
pass-through, so you do not hand-write identical one-line bodies. They are
|
|
37
|
+
deliberately independent bases rather than chained.
|
|
38
|
+
- **`EmitterDataClient`** (on `./common`, not `./browser`) — the default
|
|
39
|
+
client-side implementation of the data protocol's inbound notifications,
|
|
40
|
+
fanning each one out to a Theia `Event`: `onDidUpdateDocument`,
|
|
41
|
+
`onDidSaveDocument`, `onDidChangeProjects`. Bind an instance as the
|
|
42
|
+
`localTarget` of the frontend's RPC proxy. It sits on the common tier because
|
|
43
|
+
its only runtime dependency is `@theia/core`'s root entry, which is Theia's own
|
|
44
|
+
common tier, so a backend or a plain-Node consumer can bind it too.
|
|
45
|
+
- **`whenWorkspaceOpen`** — resolves once Theia reports a workspace root. The data
|
|
46
|
+
server only starts once the LSP launches for a workspace, so connecting earlier
|
|
47
|
+
would hang in port discovery; pass this as `whenReady`.
|
|
48
|
+
- **Backend (`./node`)** — `DataServerConnectionHandler` (the socket bridge, with
|
|
49
|
+
its own GLSP-free `SocketChannelForwarder`),
|
|
50
|
+
`createDataServerConnectionContainerModule(...handlers)` for the
|
|
51
|
+
frontend-scoped module boilerplate, and `HostDiagnosticsServer` with
|
|
52
|
+
`createHostDiagnosticsBackendModule()` — which, paired with the browser-side
|
|
53
|
+
`bindHostDiagnostics`, lights up the "Backend" diagnostics commands in
|
|
54
|
+
`@hydranium/client-theia`'s contribution.
|
|
55
|
+
|
|
56
|
+
## Install
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm install @hydranium/data-client-theia
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
You must already have a Theia application with `@theia/workspace` available, and a
|
|
63
|
+
running hydranium data server to connect to. The declared peer dependencies are:
|
|
64
|
+
|
|
65
|
+
| Peer | Range |
|
|
66
|
+
| ------------------------- | -------------------- |
|
|
67
|
+
| `@hydranium/client-theia` | `^1.0.0-next` |
|
|
68
|
+
| `@hydranium/core` | `^1.0.0-next` |
|
|
69
|
+
| `@hydranium/protocol` | `^1.0.0-next` |
|
|
70
|
+
| `@theia/core` | `^1.71.0` |
|
|
71
|
+
| `@theia/workspace` | `^1.71.0` |
|
|
72
|
+
| `inversify` | `^6.0.0` |
|
|
73
|
+
| `vscode-jsonrpc` | `^8.0.0 \|\| ^9.0.0` |
|
|
74
|
+
|
|
75
|
+
`@hydranium/core` is reached only from the `./node` tier (the host-diagnostics
|
|
76
|
+
service), so a frontend-only consumer never loads it.
|
|
77
|
+
|
|
78
|
+
## Wiring
|
|
79
|
+
|
|
80
|
+
This package declares no `theiaExtensions` — it is a library your own Theia
|
|
81
|
+
extension builds on. That extension's `package.json` declares the entries, and
|
|
82
|
+
each entry names one frontend/backend module pair:
|
|
83
|
+
|
|
84
|
+
- the **frontend** module binds your `AbstractDataServiceFrontend` subclass (and,
|
|
85
|
+
for the diagnostics commands, calls `bindHostDiagnostics`);
|
|
86
|
+
- the **backend** module is typically a one-liner:
|
|
87
|
+
`export default createDataServerConnectionContainerModule(MyHandler)`, where
|
|
88
|
+
`MyHandler` extends `DataServerConnectionHandler`.
|
|
89
|
+
|
|
90
|
+
More than one handler is the normal case, not an exotic one. Theia keys a
|
|
91
|
+
frontend channel by its service path and refuses a second channel on a path
|
|
92
|
+
already open, so every frontend abstraction reaching the data head needs its own
|
|
93
|
+
`servicePath` — while the shared `portCommand` still names the one server process
|
|
94
|
+
behind them all.
|
|
95
|
+
|
|
96
|
+
The refusal is silent, which is what makes it expensive: the loser's promise is
|
|
97
|
+
left unsettled rather than rejected, so a frontend that shared a path hangs on
|
|
98
|
+
its loading state indefinitely with nothing in the server log to say why.
|
|
99
|
+
|
|
100
|
+
## Entry points
|
|
101
|
+
|
|
102
|
+
| Subpath | Holds | Environment |
|
|
103
|
+
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- |
|
|
104
|
+
| `.` | Nothing — the surface is split by environment, so the root barrel stays empty. | browser-neutral (gated) |
|
|
105
|
+
| `./common` | `EmitterDataClient` — a module lands here only when values, types and relative imports are all neutral or Theia COMMON tier. No error-reconstruction bridge, and that is a property of the transport: the direct `vscode-jsonrpc` connection carries a typed error across the relay natively. | browser-neutral (gated) |
|
|
106
|
+
| `./browser` | `openChannelConnection`, `createChannelConnection`, the three `Abstract*DataServiceFrontend` bases, `bindHostDiagnostics`, `whenWorkspaceOpen` | browser / Theia frontend (gated) |
|
|
107
|
+
| `./node` | `DataServerConnectionHandler`, `createDataServerConnectionContainerModule`, `SocketChannelForwarder`, `HostDiagnosticsServer`, `createHostDiagnosticsBackendModule` | Node / Theia backend |
|
|
108
|
+
|
|
109
|
+
Every subpath also has a `./lib/<name>` twin for consumers on
|
|
110
|
+
`moduleResolution: "Node"`. "Gated" means the repository's neutral-bundle check
|
|
111
|
+
enforces that the entry bundles for the browser with no `node:*` import,
|
|
112
|
+
transitive ones included; `./node` is deliberately outside that gate. Also
|
|
113
|
+
worth reading: [what "gated neutral" does and does not promise](../../docs/concepts/browser-hosting.md#a-note-on-what-gated-neutral-does-and-does-not-promise).
|
|
114
|
+
|
|
115
|
+
## Status
|
|
116
|
+
|
|
117
|
+
Alpha — pre-v0, not yet published. The API is not stable and may change without a
|
|
118
|
+
deprecation cycle. See [`docs/concepts/architecture.md`](../../docs/concepts/architecture.md) for
|
|
119
|
+
the data head's place among the heads, and the
|
|
120
|
+
[repository README](../../README.md) for current status and known limitations.
|
|
121
|
+
|
|
122
|
+
## License
|
|
123
|
+
|
|
124
|
+
`MIT` — see this package's [`LICENSE`](./LICENSE). Third-party notices for the
|
|
125
|
+
runtime dependency closure are collected in the repository
|
|
126
|
+
[`NOTICE.md`](../../NOTICE.md).
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (c) 2026 CrossBreeze, 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 Channel, type Event } from '@theia/core';
|
|
10
|
+
import { type ServiceConnectionProvider } from '@theia/core/lib/browser';
|
|
11
|
+
import { type Logger, type MessageConnection } from 'vscode-jsonrpc/browser';
|
|
12
|
+
/**
|
|
13
|
+
* Build a `vscode-jsonrpc` {@link MessageConnection} on top of a Theia
|
|
14
|
+
* {@link Channel}. This is the browser-side transport for the data-server
|
|
15
|
+
* head: the frontend obtains a channel to the backend forwarder (via
|
|
16
|
+
* `connectionProvider.listen`), wraps it here, and drives the typed
|
|
17
|
+
* `createRpcProxy` over the returned connection — so the frontend
|
|
18
|
+
* speaks the same vscode-jsonrpc protocol the model-server speaks, with the
|
|
19
|
+
* backend relaying bytes between the two.
|
|
20
|
+
*
|
|
21
|
+
* Mirrors `@eclipse-glsp/theia-integration`'s `createChannelConnection`, but
|
|
22
|
+
* lives here so the data-server head carries no GLSP dependency.
|
|
23
|
+
*/
|
|
24
|
+
export declare function createChannelConnection(channel: Channel, logger?: Logger): MessageConnection;
|
|
25
|
+
/** Options for {@link openChannelConnection}. */
|
|
26
|
+
export interface OpenChannelConnectionOptions {
|
|
27
|
+
/**
|
|
28
|
+
* Resolves when it is safe to open the channel. The handler holds back the
|
|
29
|
+
* `connectionProvider.listen` call until this settles — e.g. pass
|
|
30
|
+
* `whenWorkspaceOpen` so the backend forwarder isn't asked for the
|
|
31
|
+
* model-server port before a workspace (and therefore the LSP launch that
|
|
32
|
+
* publishes the port) exists.
|
|
33
|
+
*/
|
|
34
|
+
whenReady?: Promise<void>;
|
|
35
|
+
/** Optional `vscode-jsonrpc` logger threaded into the connection. */
|
|
36
|
+
logger?: Logger;
|
|
37
|
+
/**
|
|
38
|
+
* Re-establish the connection whenever the live one is lost, by RE-OPENING
|
|
39
|
+
* the channel on `path` — not merely by rebuilding over a channel Theia hands
|
|
40
|
+
* back.
|
|
41
|
+
*
|
|
42
|
+
* **Default `true`, and the default is the point.** Re-opening is the only
|
|
43
|
+
* thing that recovers a restarted language server. Theia replays a `listen`
|
|
44
|
+
* handler solely from `ServiceConnectionProvider.handleChannelCreated`, i.e.
|
|
45
|
+
* when the MAIN frontend-backend channel is (re)created — a page reload, a
|
|
46
|
+
* dropped socket, a backend restart. A language-server restart closes just the
|
|
47
|
+
* multiplexed sub-channel for this service path while the main channel stays
|
|
48
|
+
* open, so no replacement channel ever arrives on its own and a handle that
|
|
49
|
+
* only waits for one holds a connection to the dead process forever: its
|
|
50
|
+
* requests neither answer nor reject.
|
|
51
|
+
*
|
|
52
|
+
* **Why on by default, where the older rebuild-on-a-fresh-channel behaviour
|
|
53
|
+
* was opt-in.** That one had a working alternative to weigh against — the
|
|
54
|
+
* existing connection was fine, and opting in only decided whether to swap to
|
|
55
|
+
* a fresher one, so leaving a consumer's readiness gate (and the progress UI it
|
|
56
|
+
* drives) undisturbed was worth more. A CLOSED channel offers no such choice:
|
|
57
|
+
* the alternative to re-opening is a permanently dead handle. Re-running a
|
|
58
|
+
* readiness gate against a live replacement is what a restarted server should
|
|
59
|
+
* cost.
|
|
60
|
+
*
|
|
61
|
+
* Turn it off for a consumer that treats transport loss as terminal and tears
|
|
62
|
+
* itself down instead. Off means build-once: every channel after the first is
|
|
63
|
+
* ignored and {@link ChannelConnectionHandle.onDidLoseConnection} never fires.
|
|
64
|
+
*/
|
|
65
|
+
reconnect?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Delay before the re-open following each CONSECUTIVE connection loss, in ms;
|
|
68
|
+
* the last entry repeats once the schedule is exhausted. Defaults to
|
|
69
|
+
* {@link DEFAULT_RECONNECT_DELAYS}.
|
|
70
|
+
*
|
|
71
|
+
* A delay rather than an immediate re-open, for two independent reasons.
|
|
72
|
+
* Theia's `ChannelMultiplexer.handleClose` fires the close emitter BEFORE it
|
|
73
|
+
* deletes the id from `openChannels`, so a re-open issued from inside the
|
|
74
|
+
* close listener can still see the id as open — and `open()` throws
|
|
75
|
+
* "Another channel with the id '<id>' is already open" then, which
|
|
76
|
+
* `ServiceConnectionProvider.listen` neither catches nor reports: the handler
|
|
77
|
+
* is simply never invoked, and the consumer hangs rather than failing. And a
|
|
78
|
+
* server that is flapping would otherwise be re-opened against at whatever
|
|
79
|
+
* rate it can close a channel, which is what the escalation is for.
|
|
80
|
+
*
|
|
81
|
+
* The escalation resets once a connection has survived
|
|
82
|
+
* {@link RECONNECT_ESCALATION_RESET_MS}, so an hour-long session with
|
|
83
|
+
* occasional restarts does not converge on the longest delay.
|
|
84
|
+
*/
|
|
85
|
+
reconnectDelays?: readonly number[];
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Default {@link OpenChannelConnectionOptions.reconnectDelays}.
|
|
89
|
+
*
|
|
90
|
+
* The first entry only has to outlast the synchronous close dispatch, so it is
|
|
91
|
+
* short: the backend forwarder, not this delay, is what absorbs a server that is
|
|
92
|
+
* still down. `AbstractSocketForwardingConnectionHandler` re-runs its port command per
|
|
93
|
+
* connection and retries it indefinitely, so a channel re-opened while the
|
|
94
|
+
* server is restarting parks in `findPort` and connects when the replacement
|
|
95
|
+
* publishes its new port. The later entries exist for the case that machinery
|
|
96
|
+
* cannot absorb — a server that accepts a socket and then dies again.
|
|
97
|
+
*/
|
|
98
|
+
export declare const DEFAULT_RECONNECT_DELAYS: readonly number[];
|
|
99
|
+
/** How long a connection must survive before the reconnect escalation resets. */
|
|
100
|
+
export declare const RECONNECT_ESCALATION_RESET_MS = 30000;
|
|
101
|
+
/**
|
|
102
|
+
* A live channel connection plus the two things a bare
|
|
103
|
+
* `Promise<MessageConnection>` cannot express: that the connection can be
|
|
104
|
+
* REPLACED, and that it can be released.
|
|
105
|
+
*/
|
|
106
|
+
export interface ChannelConnectionHandle {
|
|
107
|
+
/**
|
|
108
|
+
* The current connection. Read it per use rather than caching it: after a
|
|
109
|
+
* reconnect this returns a NEW promise, and a cached one resolves to the
|
|
110
|
+
* dead connection forever. The same reasoning makes
|
|
111
|
+
* `AbstractDataServiceFrontend` rebuild its proxy rather than hold one.
|
|
112
|
+
*/
|
|
113
|
+
readonly current: Promise<MessageConnection>;
|
|
114
|
+
/**
|
|
115
|
+
* Fires when the live connection is gone and a replacement is on the way.
|
|
116
|
+
* {@link current} has ALREADY been repointed at the replacement (still
|
|
117
|
+
* unresolved) by the time listeners run, so a listener re-derives from
|
|
118
|
+
* `current` and whatever it queues waits for the new channel instead of being
|
|
119
|
+
* addressed at the dead one.
|
|
120
|
+
*
|
|
121
|
+
* **This is the event a consumer wants, not {@link onDidReconnect}.** Anything
|
|
122
|
+
* derived from a connection — an `createRpcProxy`, a readiness gate, a
|
|
123
|
+
* `DataSession` generation — is dead from this moment, and a consumer that
|
|
124
|
+
* waits for the replacement to be live instead keeps sending into the dead one
|
|
125
|
+
* for the whole gap. A `DataPort` implementation translates this into the
|
|
126
|
+
* port's own `onDispose`, which is what makes `DataSession` drop its
|
|
127
|
+
* generation.
|
|
128
|
+
*
|
|
129
|
+
* Never fires when {@link OpenChannelConnectionOptions.reconnect} is off.
|
|
130
|
+
*/
|
|
131
|
+
readonly onDidLoseConnection: Event<void>;
|
|
132
|
+
/**
|
|
133
|
+
* Fires with each connection built AFTER the first, once it is live. For a
|
|
134
|
+
* consumer that needs the connection object itself; for rebuilding what was
|
|
135
|
+
* derived from the previous one, use {@link onDidLoseConnection} instead —
|
|
136
|
+
* it fires at the start of the gap rather than at its end.
|
|
137
|
+
*
|
|
138
|
+
* Never fires when {@link OpenChannelConnectionOptions.reconnect} is off.
|
|
139
|
+
*/
|
|
140
|
+
readonly onDidReconnect: Event<MessageConnection>;
|
|
141
|
+
/**
|
|
142
|
+
* Dispose the live connection and stop tracking the channel. Idempotent.
|
|
143
|
+
*
|
|
144
|
+
* Without it, a frontend being torn down leaves its connection and its
|
|
145
|
+
* inbound handler bindings attached — a leak that accumulates per cycle in
|
|
146
|
+
* tests and in a reloading workbench.
|
|
147
|
+
*/
|
|
148
|
+
dispose(): void;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Open a Theia channel to a backend forwarder and wrap it as the
|
|
152
|
+
* `vscode-jsonrpc` {@link MessageConnection} the data-server proxy speaks over.
|
|
153
|
+
*
|
|
154
|
+
* Awaits `options.whenReady` (if given), then registers the channel handler via
|
|
155
|
+
* `connectionProvider.listen` and keeps a live connection to `path` for as long
|
|
156
|
+
* as the handle is undisposed — re-opening the channel whenever the current one
|
|
157
|
+
* closes, which {@link OpenChannelConnectionOptions.reconnect} governs. Callers
|
|
158
|
+
* build their `createRpcProxy` / `bindRpcMethods` over
|
|
159
|
+
* {@link ChannelConnectionHandle.current}, which queues outbound calls and
|
|
160
|
+
* inbound bindings until the channel is live.
|
|
161
|
+
*
|
|
162
|
+
* **The `reconnect` argument to Theia's `listen` is deliberately `false`**, in
|
|
163
|
+
* both modes. Passing `true` there registers the handler for replay from
|
|
164
|
+
* `handleChannelCreated`, and this handle then has two independent re-openers for
|
|
165
|
+
* one path: Theia's replay when the main frontend-backend channel is recreated,
|
|
166
|
+
* and the close-driven re-open below — which also fires then, because
|
|
167
|
+
* `ChannelMultiplexer.onUnderlyingChannelClose` closes every sub-channel. Two
|
|
168
|
+
* `open()` calls for one id means the loser throws "Another channel with the id
|
|
169
|
+
* '<id>' is already open" inside a promise `listen` neither awaits nor reports,
|
|
170
|
+
* so the consumer's request is left unsettled: a hang with a clean log, not an
|
|
171
|
+
* error. Owning the re-open here means owning it exclusively.
|
|
172
|
+
*/
|
|
173
|
+
export declare function openChannelConnection(connectionProvider: ServiceConnectionProvider, path: string, options?: OpenChannelConnectionOptions): ChannelConnectionHandle;
|
|
174
|
+
//# sourceMappingURL=channel-connection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel-connection.d.ts","sourceRoot":"","sources":["../../src/browser/channel-connection.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EAAE,KAAK,OAAO,EAAiC,KAAK,KAAK,EAAwB,MAAM,aAAa,CAAC;AAC5G,OAAO,EAAE,KAAK,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AAEzE,OAAO,EAMJ,KAAK,MAAM,EAEX,KAAK,iBAAiB,EAQxB,MAAM,wBAAwB,CAAC;AAkEhC;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAI5F;AAED,iDAAiD;AACjD,MAAM,WAAW,4BAA4B;IAC1C;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;;;;;;;;;;;;;;;;OAkBG;IACH,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,wBAAwB,EAAE,SAAS,MAAM,EAA2C,CAAC;AAElG,iFAAiF;AACjF,eAAO,MAAM,6BAA6B,QAAS,CAAC;AAEpD;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAE7C;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,mBAAmB,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IAE1C;;;;;;;OAOG;IACH,QAAQ,CAAC,cAAc,EAAE,KAAK,CAAC,iBAAiB,CAAC,CAAC;IAElD;;;;;;OAMG;IACH,OAAO,IAAI,IAAI,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,qBAAqB,CAClC,kBAAkB,EAAE,yBAAyB,EAC7C,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,4BAAiC,GAC1C,uBAAuB,CA4IzB"}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/********************************************************************************
|
|
3
|
+
* Copyright (c) 2026 CrossBreeze, 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.RECONNECT_ESCALATION_RESET_MS = exports.DEFAULT_RECONNECT_DELAYS = void 0;
|
|
12
|
+
exports.createChannelConnection = createChannelConnection;
|
|
13
|
+
exports.openChannelConnection = openChannelConnection;
|
|
14
|
+
const core_1 = require("@theia/core");
|
|
15
|
+
const promise_util_1 = require("@theia/core/lib/common/promise-util");
|
|
16
|
+
const browser_1 = require("vscode-jsonrpc/browser");
|
|
17
|
+
/**
|
|
18
|
+
* A `vscode-jsonrpc` {@link MessageReader} that reads JSON-RPC messages off a
|
|
19
|
+
* Theia {@link Channel}. Each Theia channel message carries exactly one
|
|
20
|
+
* JSON-RPC message (the channel does its own length framing); the reader
|
|
21
|
+
* decodes the bytes and fires the parsed message.
|
|
22
|
+
*/
|
|
23
|
+
class ChannelMessageReader extends browser_1.AbstractMessageReader {
|
|
24
|
+
channel;
|
|
25
|
+
onMessageEmitter = new core_1.Emitter();
|
|
26
|
+
toDispose = new core_1.DisposableCollection();
|
|
27
|
+
constructor(channel) {
|
|
28
|
+
super();
|
|
29
|
+
this.channel = channel;
|
|
30
|
+
this.toDispose.push(this.onMessageEmitter);
|
|
31
|
+
this.toDispose.push(channel.onMessage(provider => this.handleMessage(provider)));
|
|
32
|
+
this.toDispose.push(channel.onClose(() => this.fireClose()));
|
|
33
|
+
}
|
|
34
|
+
handleMessage(provider) {
|
|
35
|
+
const buffer = provider().readBytes();
|
|
36
|
+
const message = JSON.parse(new TextDecoder().decode(buffer));
|
|
37
|
+
this.onMessageEmitter.fire(message);
|
|
38
|
+
}
|
|
39
|
+
listen(callback) {
|
|
40
|
+
return this.onMessageEmitter.event(callback);
|
|
41
|
+
}
|
|
42
|
+
dispose() {
|
|
43
|
+
super.dispose();
|
|
44
|
+
this.toDispose.dispose();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A `vscode-jsonrpc` {@link MessageWriter} that writes JSON-RPC messages onto a
|
|
49
|
+
* Theia {@link Channel} — one channel message per JSON-RPC message, the dual of
|
|
50
|
+
* {@link ChannelMessageReader}. Uses `TextEncoder` (not node `Buffer`) so the
|
|
51
|
+
* writer is browser-native, since the frontend constructs the connection.
|
|
52
|
+
*/
|
|
53
|
+
class ChannelMessageWriter extends browser_1.AbstractMessageWriter {
|
|
54
|
+
channel;
|
|
55
|
+
toDispose;
|
|
56
|
+
constructor(channel) {
|
|
57
|
+
super();
|
|
58
|
+
this.channel = channel;
|
|
59
|
+
this.toDispose = channel.onClose(() => this.fireClose());
|
|
60
|
+
}
|
|
61
|
+
write(message) {
|
|
62
|
+
const writeBuffer = this.channel.getWriteBuffer();
|
|
63
|
+
writeBuffer.writeBytes(new TextEncoder().encode(JSON.stringify(message)));
|
|
64
|
+
writeBuffer.commit();
|
|
65
|
+
return Promise.resolve();
|
|
66
|
+
}
|
|
67
|
+
end() {
|
|
68
|
+
this.dispose();
|
|
69
|
+
}
|
|
70
|
+
dispose() {
|
|
71
|
+
super.dispose();
|
|
72
|
+
this.toDispose.dispose();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Build a `vscode-jsonrpc` {@link MessageConnection} on top of a Theia
|
|
77
|
+
* {@link Channel}. This is the browser-side transport for the data-server
|
|
78
|
+
* head: the frontend obtains a channel to the backend forwarder (via
|
|
79
|
+
* `connectionProvider.listen`), wraps it here, and drives the typed
|
|
80
|
+
* `createRpcProxy` over the returned connection — so the frontend
|
|
81
|
+
* speaks the same vscode-jsonrpc protocol the model-server speaks, with the
|
|
82
|
+
* backend relaying bytes between the two.
|
|
83
|
+
*
|
|
84
|
+
* Mirrors `@eclipse-glsp/theia-integration`'s `createChannelConnection`, but
|
|
85
|
+
* lives here so the data-server head carries no GLSP dependency.
|
|
86
|
+
*/
|
|
87
|
+
function createChannelConnection(channel, logger) {
|
|
88
|
+
const reader = new ChannelMessageReader(channel);
|
|
89
|
+
const writer = new ChannelMessageWriter(channel);
|
|
90
|
+
return (0, browser_1.createMessageConnection)(reader, writer, logger);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Default {@link OpenChannelConnectionOptions.reconnectDelays}.
|
|
94
|
+
*
|
|
95
|
+
* The first entry only has to outlast the synchronous close dispatch, so it is
|
|
96
|
+
* short: the backend forwarder, not this delay, is what absorbs a server that is
|
|
97
|
+
* still down. `AbstractSocketForwardingConnectionHandler` re-runs its port command per
|
|
98
|
+
* connection and retries it indefinitely, so a channel re-opened while the
|
|
99
|
+
* server is restarting parks in `findPort` and connects when the replacement
|
|
100
|
+
* publishes its new port. The later entries exist for the case that machinery
|
|
101
|
+
* cannot absorb — a server that accepts a socket and then dies again.
|
|
102
|
+
*/
|
|
103
|
+
exports.DEFAULT_RECONNECT_DELAYS = [250, 500, 1_000, 2_000, 4_000, 8_000];
|
|
104
|
+
/** How long a connection must survive before the reconnect escalation resets. */
|
|
105
|
+
exports.RECONNECT_ESCALATION_RESET_MS = 30_000;
|
|
106
|
+
/**
|
|
107
|
+
* Open a Theia channel to a backend forwarder and wrap it as the
|
|
108
|
+
* `vscode-jsonrpc` {@link MessageConnection} the data-server proxy speaks over.
|
|
109
|
+
*
|
|
110
|
+
* Awaits `options.whenReady` (if given), then registers the channel handler via
|
|
111
|
+
* `connectionProvider.listen` and keeps a live connection to `path` for as long
|
|
112
|
+
* as the handle is undisposed — re-opening the channel whenever the current one
|
|
113
|
+
* closes, which {@link OpenChannelConnectionOptions.reconnect} governs. Callers
|
|
114
|
+
* build their `createRpcProxy` / `bindRpcMethods` over
|
|
115
|
+
* {@link ChannelConnectionHandle.current}, which queues outbound calls and
|
|
116
|
+
* inbound bindings until the channel is live.
|
|
117
|
+
*
|
|
118
|
+
* **The `reconnect` argument to Theia's `listen` is deliberately `false`**, in
|
|
119
|
+
* both modes. Passing `true` there registers the handler for replay from
|
|
120
|
+
* `handleChannelCreated`, and this handle then has two independent re-openers for
|
|
121
|
+
* one path: Theia's replay when the main frontend-backend channel is recreated,
|
|
122
|
+
* and the close-driven re-open below — which also fires then, because
|
|
123
|
+
* `ChannelMultiplexer.onUnderlyingChannelClose` closes every sub-channel. Two
|
|
124
|
+
* `open()` calls for one id means the loser throws "Another channel with the id
|
|
125
|
+
* '<id>' is already open" inside a promise `listen` neither awaits nor reports,
|
|
126
|
+
* so the consumer's request is left unsettled: a hang with a clean log, not an
|
|
127
|
+
* error. Owning the re-open here means owning it exclusively.
|
|
128
|
+
*/
|
|
129
|
+
function openChannelConnection(connectionProvider, path, options = {}) {
|
|
130
|
+
const reconnect = options.reconnect ?? true;
|
|
131
|
+
const reconnectDelays = options.reconnectDelays ?? exports.DEFAULT_RECONNECT_DELAYS;
|
|
132
|
+
const reconnectEmitter = new core_1.Emitter();
|
|
133
|
+
const loseEmitter = new core_1.Emitter();
|
|
134
|
+
let deferred = new promise_util_1.Deferred();
|
|
135
|
+
let live;
|
|
136
|
+
let generation = 0;
|
|
137
|
+
let disposed = false;
|
|
138
|
+
/** Set from the close that repointed `deferred`, cleared by the accept that resolves it. */
|
|
139
|
+
let awaitingReplacement = false;
|
|
140
|
+
/** Consecutive losses, indexing `reconnectDelays`. */
|
|
141
|
+
let losses = 0;
|
|
142
|
+
/** When the live connection was built, for the escalation reset. `0` before the first. */
|
|
143
|
+
let liveSince = 0;
|
|
144
|
+
let reopenTimer;
|
|
145
|
+
// Own synchronous flag, deliberately NOT `deferred.state`. Theia's
|
|
146
|
+
// `Deferred` sets `state` inside a `.then()` on its own promise, so it is
|
|
147
|
+
// still `'unresolved'` for a microtask after `resolve()` returns. Guarding on
|
|
148
|
+
// it means two channels arriving in the SAME synchronous turn both pass the
|
|
149
|
+
// guard: the second connection gets built and starts listening, the
|
|
150
|
+
// first-wins `resolve` keeps the promise pointed at the first, and the second
|
|
151
|
+
// leaks — attached to a channel, reachable by nobody, never disposed. A test
|
|
152
|
+
// that only checks which connection the promise resolves with cannot see
|
|
153
|
+
// that; it takes counting the connections built per channel.
|
|
154
|
+
let built = false;
|
|
155
|
+
const scheduleReopen = () => {
|
|
156
|
+
if (liveSince > 0 && Date.now() - liveSince >= exports.RECONNECT_ESCALATION_RESET_MS) {
|
|
157
|
+
losses = 0;
|
|
158
|
+
}
|
|
159
|
+
const delay = reconnectDelays[Math.min(losses, reconnectDelays.length - 1)];
|
|
160
|
+
losses++;
|
|
161
|
+
reopenTimer = setTimeout(() => {
|
|
162
|
+
reopenTimer = undefined;
|
|
163
|
+
// `awaitingReplacement` is the guard against re-opening a path that
|
|
164
|
+
// already has a channel: a second `open()` for one id is the "already
|
|
165
|
+
// open" throw, and its symptom is a hang rather than an error.
|
|
166
|
+
if (disposed || !awaitingReplacement) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
requestChannel();
|
|
170
|
+
}, delay);
|
|
171
|
+
};
|
|
172
|
+
/**
|
|
173
|
+
* The live connection's channel closed. Retire the generation and arm the
|
|
174
|
+
* replacement, in that order — a listener on `onDidLoseConnection` must
|
|
175
|
+
* already see `current` pointing at the replacement.
|
|
176
|
+
*/
|
|
177
|
+
const handleChannelClosed = () => {
|
|
178
|
+
if (disposed || awaitingReplacement) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
// Dispose rather than merely drop. vscode-jsonrpc rejects pending
|
|
182
|
+
// responses from `dispose`, never from a reader-side close — so a
|
|
183
|
+
// connection left undisposed keeps every in-flight request unsettled
|
|
184
|
+
// forever, which is the failure this whole path exists to end.
|
|
185
|
+
live?.dispose();
|
|
186
|
+
live = undefined;
|
|
187
|
+
deferred = new promise_util_1.Deferred();
|
|
188
|
+
awaitingReplacement = true;
|
|
189
|
+
loseEmitter.fire(undefined);
|
|
190
|
+
scheduleReopen();
|
|
191
|
+
};
|
|
192
|
+
const acceptChannel = (channel) => {
|
|
193
|
+
if (disposed) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (built && !awaitingReplacement) {
|
|
197
|
+
// Unsolicited: a channel arrived while one is already live and no close
|
|
198
|
+
// asked for a replacement. Exactly one channel is outstanding at a time
|
|
199
|
+
// here, so this is either the build-once case (`reconnect` off) or a
|
|
200
|
+
// handler re-fire, and adopting it would leave the previous connection
|
|
201
|
+
// listening on its own channel, reachable by nobody and never disposed.
|
|
202
|
+
// The close-driven path is the only way a second generation is built.
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const connection = createChannelConnection(channel, options.logger);
|
|
206
|
+
connection.listen();
|
|
207
|
+
live = connection;
|
|
208
|
+
built = true;
|
|
209
|
+
awaitingReplacement = false;
|
|
210
|
+
generation++;
|
|
211
|
+
liveSince = Date.now();
|
|
212
|
+
if (reconnect) {
|
|
213
|
+
// Registered AFTER `createChannelConnection`, so the connection's own
|
|
214
|
+
// reader and writer see the close first and `connection.onClose` still
|
|
215
|
+
// fires for whoever is listening to it. Disposing before they run would
|
|
216
|
+
// put the connection in `Disposed`, from which vscode-jsonrpc
|
|
217
|
+
// deliberately emits no close event at all.
|
|
218
|
+
const acceptedGeneration = generation;
|
|
219
|
+
channel.onClose(() => {
|
|
220
|
+
if (acceptedGeneration === generation) {
|
|
221
|
+
handleChannelClosed();
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
deferred.resolve(connection);
|
|
226
|
+
if (generation > 1) {
|
|
227
|
+
reconnectEmitter.fire(connection);
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
/** Register the channel handler for one open attempt on `path`. */
|
|
231
|
+
function requestChannel() {
|
|
232
|
+
connectionProvider.listen(path, (_path, channel) => acceptChannel(channel), false);
|
|
233
|
+
}
|
|
234
|
+
const start = async () => {
|
|
235
|
+
await options.whenReady;
|
|
236
|
+
if (disposed) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
requestChannel();
|
|
240
|
+
};
|
|
241
|
+
start();
|
|
242
|
+
return {
|
|
243
|
+
get current() {
|
|
244
|
+
return deferred.promise;
|
|
245
|
+
},
|
|
246
|
+
onDidLoseConnection: loseEmitter.event,
|
|
247
|
+
onDidReconnect: reconnectEmitter.event,
|
|
248
|
+
dispose() {
|
|
249
|
+
if (disposed) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
disposed = true;
|
|
253
|
+
if (reopenTimer !== undefined) {
|
|
254
|
+
clearTimeout(reopenTimer);
|
|
255
|
+
reopenTimer = undefined;
|
|
256
|
+
}
|
|
257
|
+
live?.dispose();
|
|
258
|
+
live = undefined;
|
|
259
|
+
loseEmitter.dispose();
|
|
260
|
+
reconnectEmitter.dispose();
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
//# sourceMappingURL=channel-connection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel-connection.js","sourceRoot":"","sources":["../../src/browser/channel-connection.ts"],"names":[],"mappings":"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"}
|