@hydranium/data-client-theia 1.0.0-next.28 → 1.0.0-next.280
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 +57 -101
- package/lib/browser/channel-connection.d.ts +11 -8
- package/lib/browser/channel-connection.d.ts.map +1 -1
- package/lib/browser/channel-connection.js +7 -5
- package/lib/browser/channel-connection.js.map +1 -1
- package/lib/browser/channel-data-port.d.ts +92 -0
- package/lib/browser/channel-data-port.d.ts.map +1 -0
- package/lib/browser/channel-data-port.js +199 -0
- package/lib/browser/channel-data-port.js.map +1 -0
- package/lib/browser/data-session-stop-contribution.d.ts +76 -0
- package/lib/browser/data-session-stop-contribution.d.ts.map +1 -0
- package/lib/browser/data-session-stop-contribution.js +146 -0
- package/lib/browser/data-session-stop-contribution.js.map +1 -0
- package/lib/browser/host-diagnostics-frontend.js +1 -1
- package/lib/browser/host-diagnostics-frontend.js.map +1 -1
- package/lib/browser/index.d.ts +2 -3
- package/lib/browser/index.d.ts.map +1 -1
- package/lib/browser/index.js +2 -3
- package/lib/browser/index.js.map +1 -1
- package/lib/browser/workspace-gate.js.map +1 -1
- package/lib/common/emitter-data-client.d.ts +13 -1
- package/lib/common/emitter-data-client.d.ts.map +1 -1
- package/lib/common/emitter-data-client.js +18 -0
- package/lib/common/emitter-data-client.js.map +1 -1
- package/lib/node/connection-container-module.d.ts +5 -4
- package/lib/node/connection-container-module.d.ts.map +1 -1
- package/lib/node/connection-container-module.js +5 -4
- package/lib/node/connection-container-module.js.map +1 -1
- package/lib/node/data-server-connection-handler.d.ts +9 -19
- package/lib/node/data-server-connection-handler.d.ts.map +1 -1
- package/lib/node/data-server-connection-handler.js +9 -13
- package/lib/node/data-server-connection-handler.js.map +1 -1
- package/lib/node/host-diagnostics-server.d.ts.map +1 -1
- package/lib/node/host-diagnostics-server.js +1 -1
- package/lib/node/host-diagnostics-server.js.map +1 -1
- package/lib/node/index.d.ts +0 -1
- package/lib/node/index.d.ts.map +1 -1
- package/lib/node/index.js +0 -1
- package/lib/node/index.js.map +1 -1
- package/package.json +25 -39
- package/src/browser/channel-connection.ts +16 -12
- package/src/browser/channel-data-port.ts +189 -0
- package/src/browser/data-session-stop-contribution.ts +151 -0
- package/src/browser/host-diagnostics-frontend.ts +1 -1
- package/src/browser/index.ts +2 -3
- package/src/common/emitter-data-client.ts +27 -0
- package/src/node/connection-container-module.ts +5 -4
- package/src/node/data-server-connection-handler.ts +10 -23
- package/src/node/host-diagnostics-server.ts +1 -1
- package/src/node/index.ts +0 -1
- package/lib/browser/data-service-frontend.d.ts +0 -154
- package/lib/browser/data-service-frontend.d.ts.map +0 -1
- package/lib/browser/data-service-frontend.js +0 -169
- package/lib/browser/data-service-frontend.js.map +0 -1
- package/lib/browser/diagnostics-data-service-frontend.d.ts +0 -35
- package/lib/browser/diagnostics-data-service-frontend.d.ts.map +0 -1
- package/lib/browser/diagnostics-data-service-frontend.js +0 -54
- package/lib/browser/diagnostics-data-service-frontend.js.map +0 -1
- package/lib/browser/references-data-service-frontend.d.ts +0 -45
- package/lib/browser/references-data-service-frontend.d.ts.map +0 -1
- package/lib/browser/references-data-service-frontend.js +0 -55
- package/lib/browser/references-data-service-frontend.js.map +0 -1
- package/lib/node/socket-channel-forwarder.d.ts +0 -34
- package/lib/node/socket-channel-forwarder.d.ts.map +0 -1
- package/lib/node/socket-channel-forwarder.js +0 -68
- package/lib/node/socket-channel-forwarder.js.map +0 -1
- package/src/browser/data-service-frontend.ts +0 -208
- package/src/browser/diagnostics-data-service-frontend.ts +0 -70
- package/src/browser/references-data-service-frontend.ts +0 -70
- package/src/node/socket-channel-forwarder.ts +0 -69
package/README.md
CHANGED
|
@@ -1,57 +1,20 @@
|
|
|
1
1
|
# `@hydranium/data-client-theia`
|
|
2
2
|
|
|
3
|
-
Theia
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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.
|
|
3
|
+
The Theia connection for the
|
|
4
|
+
[Hydranium](https://github.com/eclipse-emfcloud/hydranium) data head, the
|
|
5
|
+
companion of `@hydranium/data-server`. Install it when forms, trees or views in
|
|
6
|
+
a Theia application work on the live model rather than on text. It builds on
|
|
7
|
+
`@hydranium/client-theia`.
|
|
13
8
|
|
|
14
9
|
## What it gives you
|
|
15
10
|
|
|
16
|
-
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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.
|
|
11
|
+
- A data connection in the Theia frontend: you subclass `ChannelDataPort`, and
|
|
12
|
+
the connection and sessions from `@hydranium/protocol` run over it.
|
|
13
|
+
- A connection that waits for a workspace, and reconnects on its own when the
|
|
14
|
+
channel is lost, a restarted server included.
|
|
15
|
+
- Sessions that end with the page, and a page held while one of them saves.
|
|
16
|
+
- The backend handler that relays the channel to the data server's socket.
|
|
17
|
+
- Diagnostics commands for the Theia backend process, beside the server's.
|
|
55
18
|
|
|
56
19
|
## Install
|
|
57
20
|
|
|
@@ -59,68 +22,61 @@ code-gen driven from the live AST rather than from LSP text edits.
|
|
|
59
22
|
npm install @hydranium/data-client-theia
|
|
60
23
|
```
|
|
61
24
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
|
66
|
-
|
|
|
67
|
-
| `@
|
|
68
|
-
|
|
|
69
|
-
|
|
|
70
|
-
| `@theia/core` | `^1.70.0` |
|
|
71
|
-
| `@theia/workspace` | `^1.70.0` |
|
|
72
|
-
| `inversify` | `^6.0.0` |
|
|
73
|
-
| `vscode-jsonrpc` | `9.0.1` |
|
|
74
|
-
|
|
75
|
-
`@hydranium/core` is reached only from the `./node` tier (the host-diagnostics
|
|
76
|
-
service), so a frontend-only consumer never loads it.
|
|
25
|
+
| Peer | Range |
|
|
26
|
+
| ------------------------- | ------------- |
|
|
27
|
+
| `@hydranium/client-theia` | `^1.0.0-next` |
|
|
28
|
+
| `@hydranium/protocol` | `^1.0.0-next` |
|
|
29
|
+
| `@theia/core` | `^1.70.0` |
|
|
30
|
+
| `@theia/workspace` | `^1.70.0` |
|
|
31
|
+
| `inversify` | `^6.0.0` |
|
|
32
|
+
| `vscode-jsonrpc` | `^9.0.0` |
|
|
77
33
|
|
|
78
34
|
## Wiring
|
|
79
35
|
|
|
80
|
-
This package declares no `theiaExtensions
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
- the
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
`
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
36
|
+
This package declares no `theiaExtensions`; your own Theia extension declares
|
|
37
|
+
them and binds from here.
|
|
38
|
+
|
|
39
|
+
- In a frontend module, subclass `ChannelDataPort` with a `servicePath` and
|
|
40
|
+
bind it in singleton scope. Call `bindDataConnection` for the connection
|
|
41
|
+
over it.
|
|
42
|
+
- In the same frontend, call `bindChannelLogger` from
|
|
43
|
+
`@hydranium/client-theia`, and its `bindEditorDiskSync`, since a data server
|
|
44
|
+
save writes a file an editor can have open.
|
|
45
|
+
- For the backend diagnostics commands, call `bindHostDiagnostics` in the
|
|
46
|
+
frontend and export `createHostDiagnosticsBackendModule()` from a backend
|
|
47
|
+
module.
|
|
48
|
+
- In the backend, export
|
|
49
|
+
`createDataServerConnectionContainerModule(MyHandler)`, where `MyHandler`
|
|
50
|
+
extends `DataServerConnectionHandler` and names your server's port command.
|
|
51
|
+
|
|
52
|
+
Give each frontend channel its own `servicePath`. Theia refuses a second
|
|
53
|
+
channel on a path already open, silently: the frontend hangs with nothing in
|
|
54
|
+
the log. For several participants on one channel, use sessions instead.
|
|
55
|
+
|
|
56
|
+
For the whole setup, see *Host in Theia* in
|
|
57
|
+
[Adopting Hydranium](https://github.com/eclipse-emfcloud/hydranium/blob/main/docs/ADOPTING.md).
|
|
99
58
|
|
|
100
59
|
## Entry points
|
|
101
60
|
|
|
102
|
-
| Subpath |
|
|
103
|
-
| ----------- |
|
|
104
|
-
| `.` | Nothing
|
|
105
|
-
| `./common` |
|
|
106
|
-
| `./browser` |
|
|
107
|
-
| `./node` |
|
|
61
|
+
| Subpath | Use it for | Runs in |
|
|
62
|
+
| ----------- | ------------------------------------------------------------- | --------------- |
|
|
63
|
+
| `.` | Nothing; import a subpath | browser-neutral |
|
|
64
|
+
| `./common` | The data server's notifications as Theia events, on any side | browser-neutral |
|
|
65
|
+
| `./browser` | The port, the connection binding and the diagnostics proxy | Theia frontend |
|
|
66
|
+
| `./node` | The connection handler and the backend modules | Theia backend |
|
|
108
67
|
|
|
109
|
-
|
|
110
|
-
`
|
|
111
|
-
|
|
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).
|
|
68
|
+
The subpaths need a TypeScript `moduleResolution` that reads `exports`
|
|
69
|
+
(`NodeNext` or `Bundler`); see *Requirements* in
|
|
70
|
+
[Adopting Hydranium](https://github.com/eclipse-emfcloud/hydranium/blob/main/docs/ADOPTING.md).
|
|
114
71
|
|
|
115
72
|
## Status
|
|
116
73
|
|
|
117
|
-
Alpha
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
[repository README](../../README.md) for current status and known limitations.
|
|
74
|
+
Alpha: every release is a prerelease that may break the API, so pin an exact
|
|
75
|
+
version. Guides and known limitations:
|
|
76
|
+
[Adopting Hydranium](https://github.com/eclipse-emfcloud/hydranium/blob/main/docs/ADOPTING.md).
|
|
121
77
|
|
|
122
78
|
## License
|
|
123
79
|
|
|
124
|
-
`MIT` — see this package's [`LICENSE`](./LICENSE)
|
|
125
|
-
|
|
126
|
-
|
|
80
|
+
`MIT` — see this package's [`LICENSE`](./LICENSE), and the repository
|
|
81
|
+
[`NOTICE.md`](https://github.com/eclipse-emfcloud/hydranium/blob/main/NOTICE.md)
|
|
82
|
+
for third-party notices.
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* SPDX-License-Identifier: MIT
|
|
8
8
|
********************************************************************************/
|
|
9
|
+
import { type Clock } from '@hydranium/protocol';
|
|
9
10
|
import { type Channel, type Event } from '@theia/core';
|
|
10
11
|
import { type ServiceConnectionProvider } from '@theia/core/lib/browser';
|
|
11
12
|
import { type Logger, type MessageConnection } from 'vscode-jsonrpc/browser';
|
|
@@ -21,7 +22,7 @@ import { type Logger, type MessageConnection } from 'vscode-jsonrpc/browser';
|
|
|
21
22
|
* Mirrors `@eclipse-glsp/theia-integration`'s `createChannelConnection`, but
|
|
22
23
|
* lives here so the data-server head carries no GLSP dependency.
|
|
23
24
|
*/
|
|
24
|
-
export declare function createChannelConnection(channel: Channel, logger
|
|
25
|
+
export declare function createChannelConnection(channel: Channel, logger: Logger): MessageConnection;
|
|
25
26
|
/** Options for {@link openChannelConnection}. */
|
|
26
27
|
export interface OpenChannelConnectionOptions {
|
|
27
28
|
/**
|
|
@@ -32,8 +33,10 @@ export interface OpenChannelConnectionOptions {
|
|
|
32
33
|
* publishes the port) exists.
|
|
33
34
|
*/
|
|
34
35
|
whenReady?: Promise<void>;
|
|
35
|
-
/**
|
|
36
|
-
logger
|
|
36
|
+
/** Where the connection logs its protocol faults; without one they leave no trace. */
|
|
37
|
+
logger: Logger;
|
|
38
|
+
/** Times the escalation reset. Default a `SystemClock`. */
|
|
39
|
+
clock?: Clock;
|
|
37
40
|
/**
|
|
38
41
|
* Re-establish the connection whenever the live one is lost, by RE-OPENING
|
|
39
42
|
* the channel on `path` — not merely by rebuilding over a channel Theia hands
|
|
@@ -107,8 +110,8 @@ export interface ChannelConnectionHandle {
|
|
|
107
110
|
/**
|
|
108
111
|
* The current connection. Read it per use rather than caching it: after a
|
|
109
112
|
* reconnect this returns a NEW promise, and a cached one resolves to the
|
|
110
|
-
* dead connection forever. The same reasoning makes
|
|
111
|
-
*
|
|
113
|
+
* dead connection forever. The same reasoning makes `RpcConnection` rebuild
|
|
114
|
+
* its proxy per generation rather than hold one.
|
|
112
115
|
*/
|
|
113
116
|
readonly current: Promise<MessageConnection>;
|
|
114
117
|
/**
|
|
@@ -120,10 +123,10 @@ export interface ChannelConnectionHandle {
|
|
|
120
123
|
*
|
|
121
124
|
* **This is the event a consumer wants, not {@link onDidReconnect}.** Anything
|
|
122
125
|
* derived from a connection — an `createRpcProxy`, a readiness gate, a
|
|
123
|
-
* `
|
|
126
|
+
* `DataConnection` generation — is dead from this moment, and a consumer that
|
|
124
127
|
* waits for the replacement to be live instead keeps sending into the dead one
|
|
125
128
|
* for the whole gap. A `DataPort` implementation translates this into the
|
|
126
|
-
* port's own `onDispose`, which is what makes `
|
|
129
|
+
* port's own `onDispose`, which is what makes `DataConnection` drop its
|
|
127
130
|
* generation.
|
|
128
131
|
*
|
|
129
132
|
* Never fires when {@link OpenChannelConnectionOptions.reconnect} is off.
|
|
@@ -170,5 +173,5 @@ export interface ChannelConnectionHandle {
|
|
|
170
173
|
* so the consumer's request is left unsettled: a hang with a clean log, not an
|
|
171
174
|
* error. Owning the re-open here means owning it exclusively.
|
|
172
175
|
*/
|
|
173
|
-
export declare function openChannelConnection(connectionProvider: ServiceConnectionProvider, path: string, options
|
|
176
|
+
export declare function openChannelConnection(connectionProvider: ServiceConnectionProvider, path: string, options: OpenChannelConnectionOptions): ChannelConnectionHandle;
|
|
174
177
|
//# sourceMappingURL=channel-connection.d.ts.map
|
|
@@ -1 +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,
|
|
1
|
+
{"version":3,"file":"channel-connection.d.ts","sourceRoot":"","sources":["../../src/browser/channel-connection.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EAAE,KAAK,KAAK,EAA+B,MAAM,qBAAqB,CAAC;AAC9E,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,EAAE,MAAM,GAAG,iBAAiB,CAI3F;AAED,iDAAiD;AACjD,MAAM,WAAW,4BAA4B;IAC1C;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,sFAAsF;IACtF,MAAM,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,KAAK,CAAC,EAAE,KAAK,CAAC;IACd;;;;;;;;;;;;;;;;;;;;;;;;;;;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,EAAE,4BAA4B,GACrC,uBAAuB,CA6IzB"}
|
|
@@ -11,6 +11,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
11
11
|
exports.RECONNECT_ESCALATION_RESET_MS = exports.DEFAULT_RECONNECT_DELAYS = void 0;
|
|
12
12
|
exports.createChannelConnection = createChannelConnection;
|
|
13
13
|
exports.openChannelConnection = openChannelConnection;
|
|
14
|
+
const protocol_1 = require("@hydranium/protocol");
|
|
14
15
|
const core_1 = require("@theia/core");
|
|
15
16
|
const promise_util_1 = require("@theia/core/lib/common/promise-util");
|
|
16
17
|
const browser_1 = require("vscode-jsonrpc/browser");
|
|
@@ -126,7 +127,7 @@ exports.RECONNECT_ESCALATION_RESET_MS = 30_000;
|
|
|
126
127
|
* so the consumer's request is left unsettled: a hang with a clean log, not an
|
|
127
128
|
* error. Owning the re-open here means owning it exclusively.
|
|
128
129
|
*/
|
|
129
|
-
function openChannelConnection(connectionProvider, path, options
|
|
130
|
+
function openChannelConnection(connectionProvider, path, options) {
|
|
130
131
|
const reconnect = options.reconnect ?? true;
|
|
131
132
|
const reconnectDelays = options.reconnectDelays ?? exports.DEFAULT_RECONNECT_DELAYS;
|
|
132
133
|
const reconnectEmitter = new core_1.Emitter();
|
|
@@ -139,8 +140,9 @@ function openChannelConnection(connectionProvider, path, options = {}) {
|
|
|
139
140
|
let awaitingReplacement = false;
|
|
140
141
|
/** Consecutive losses, indexing `reconnectDelays`. */
|
|
141
142
|
let losses = 0;
|
|
142
|
-
|
|
143
|
-
|
|
143
|
+
const clock = options.clock ?? new protocol_1.SystemClock();
|
|
144
|
+
/** Running since the live connection was built, for the escalation reset. */
|
|
145
|
+
let sinceLive;
|
|
144
146
|
let reopenTimer;
|
|
145
147
|
// Own synchronous flag, deliberately NOT `deferred.state`. Theia's
|
|
146
148
|
// `Deferred` sets `state` inside a `.then()` on its own promise, so it is
|
|
@@ -153,7 +155,7 @@ function openChannelConnection(connectionProvider, path, options = {}) {
|
|
|
153
155
|
// that; it takes counting the connections built per channel.
|
|
154
156
|
let built = false;
|
|
155
157
|
const scheduleReopen = () => {
|
|
156
|
-
if (
|
|
158
|
+
if (sinceLive !== undefined && sinceLive.elapsedMs >= exports.RECONNECT_ESCALATION_RESET_MS) {
|
|
157
159
|
losses = 0;
|
|
158
160
|
}
|
|
159
161
|
const delay = reconnectDelays[Math.min(losses, reconnectDelays.length - 1)];
|
|
@@ -208,7 +210,7 @@ function openChannelConnection(connectionProvider, path, options = {}) {
|
|
|
208
210
|
built = true;
|
|
209
211
|
awaitingReplacement = false;
|
|
210
212
|
generation++;
|
|
211
|
-
|
|
213
|
+
sinceLive = clock.stopwatch();
|
|
212
214
|
if (reconnect) {
|
|
213
215
|
// Registered AFTER `createChannelConnection`, so the connection's own
|
|
214
216
|
// reader and writer see the close first and `connection.onClose` still
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel-connection.js","sourceRoot":"","sources":["../../src/browser/channel-connection.ts"],"names":[],"mappings":";AAAA;;;;;;;kFAOkF
|
|
1
|
+
{"version":3,"file":"channel-connection.js","sourceRoot":"","sources":["../../src/browser/channel-connection.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,92 @@
|
|
|
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 { ChannelLogger, ConnectionReporter, type ConnectionAttempt, type ConnectionTarget } from '@hydranium/client-theia/browser';
|
|
10
|
+
import { Clock } from '@hydranium/client-theia/common';
|
|
11
|
+
import { type DataPort, type ResolvedMessage, type RpcConnectionLifecycle } from '@hydranium/protocol';
|
|
12
|
+
import { Emitter, MessageService, type Event } from '@theia/core';
|
|
13
|
+
import { type ServiceConnectionProvider } from '@theia/core/lib/browser';
|
|
14
|
+
import { WorkspaceService } from '@theia/workspace/lib/browser';
|
|
15
|
+
import type { MessageConnection } from 'vscode-jsonrpc';
|
|
16
|
+
import { type ChannelConnectionHandle } from './channel-connection';
|
|
17
|
+
/**
|
|
18
|
+
* A {@link DataPort} over a Theia frontend channel.
|
|
19
|
+
*
|
|
20
|
+
* Subclasses supply {@link servicePath}; everything above the port —
|
|
21
|
+
* `DataConnection`, its sessions, and whatever model a widget drives — is
|
|
22
|
+
* host-neutral and shared with the VS Code and browser shells.
|
|
23
|
+
*
|
|
24
|
+
* Bind one per service path and in singleton scope. Theia keys a frontend
|
|
25
|
+
* channel by its path and refuses a second on a path already open, and the
|
|
26
|
+
* throw escapes the `openChannelConnection` the loser is awaiting, leaving that
|
|
27
|
+
* promise unsettled rather than rejected.
|
|
28
|
+
*/
|
|
29
|
+
export declare abstract class ChannelDataPort implements DataPort {
|
|
30
|
+
protected readonly connectionProvider: ServiceConnectionProvider;
|
|
31
|
+
protected readonly workspaceService: WorkspaceService;
|
|
32
|
+
protected readonly messageService: MessageService;
|
|
33
|
+
protected readonly connectionReporter: ConnectionReporter;
|
|
34
|
+
readonly logger: ChannelLogger;
|
|
35
|
+
/** Times the channel's reconnect escalation: the container's {@link Clock}, or a `SystemClock`. */
|
|
36
|
+
protected readonly clock: Clock;
|
|
37
|
+
/** Frontend service path the backend forwarder for this head is registered under. */
|
|
38
|
+
protected abstract readonly servicePath: string;
|
|
39
|
+
/**
|
|
40
|
+
* Re-open the channel when the current connection is lost. Default `true` —
|
|
41
|
+
* re-opening is the only thing that recovers a restarted language server,
|
|
42
|
+
* which binds new ephemeral ports. Turn it off for a frontend that tears
|
|
43
|
+
* itself down on transport loss instead.
|
|
44
|
+
*/
|
|
45
|
+
protected readonly reconnectOnConnectionLoss: boolean;
|
|
46
|
+
protected readonly disposeEmitter: Emitter<void>;
|
|
47
|
+
readonly onDispose: Event<void>;
|
|
48
|
+
protected handle?: ChannelConnectionHandle;
|
|
49
|
+
/** Set by {@link dispose}, after which {@link connect} rejects. */
|
|
50
|
+
protected disposed: boolean;
|
|
51
|
+
/** How long a connection may take before it is reported as failed; it keeps trying after. */
|
|
52
|
+
protected readonly connectFailureNoticeMs: number;
|
|
53
|
+
protected attempt?: ConnectionAttempt;
|
|
54
|
+
protected attemptTimer?: ReturnType<typeof setTimeout>;
|
|
55
|
+
/** Set once {@link connectionLifecycle} reports, which then owns the connection failures. */
|
|
56
|
+
protected reportsConnections: boolean;
|
|
57
|
+
/** Reports each connection generation through the {@link ConnectionReporter}. */
|
|
58
|
+
readonly connectionLifecycle: RpcConnectionLifecycle;
|
|
59
|
+
/**
|
|
60
|
+
* Open the workspace-gated channel and hand back its listening connection.
|
|
61
|
+
*
|
|
62
|
+
* Gated because the backend forwarder discovers the head's port by executing
|
|
63
|
+
* a command the language server answers, and the language server only
|
|
64
|
+
* launches once there is a workspace to launch it for. Asking earlier polls
|
|
65
|
+
* a command nobody has registered.
|
|
66
|
+
*
|
|
67
|
+
* Rejects once the port is disposed: the consumer reconnects when the
|
|
68
|
+
* dispose fires {@link onDispose}, and a channel opened then is closed by
|
|
69
|
+
* nothing.
|
|
70
|
+
*/
|
|
71
|
+
connect(): Promise<MessageConnection>;
|
|
72
|
+
/**
|
|
73
|
+
* Surface a transport or write failure as a Theia notification.
|
|
74
|
+
*
|
|
75
|
+
* Swallowing it is the failure this exists to prevent: a dead connection and
|
|
76
|
+
* an empty document are indistinguishable in a widget.
|
|
77
|
+
*
|
|
78
|
+
* `reported` is already a complete sentence with the detail interpolated, so
|
|
79
|
+
* wrapping it in a sentence of the host's own would nest one owner's clause
|
|
80
|
+
* inside another's and leave no translator in control of the whole.
|
|
81
|
+
*/
|
|
82
|
+
reportError(_error: unknown, reported: ResolvedMessage): void;
|
|
83
|
+
/** End the current attempt through `settle`, or cancel it. */
|
|
84
|
+
protected settleAttempt(settle?: (attempt: ConnectionAttempt) => void): void;
|
|
85
|
+
protected get connectionTarget(): ConnectionTarget;
|
|
86
|
+
/** One object per port: the reporter keys what it has shown on it. */
|
|
87
|
+
protected cachedConnectionTarget?: ConnectionTarget;
|
|
88
|
+
protected unreachableMessage(): string;
|
|
89
|
+
protected connectTimeoutMessage(): string;
|
|
90
|
+
dispose(): void;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=channel-data-port.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel-data-port.d.ts","sourceRoot":"","sources":["../../src/browser/channel-data-port.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnI,OAAO,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAC;AACvD,OAAO,EAKJ,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC7B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,OAAO,EAAE,cAAc,EAAO,KAAK,KAAK,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,KAAK,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AAGzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,KAAK,uBAAuB,EAAyB,MAAM,sBAAsB,CAAC;AAG3F;;;;;;;;;;;GAWG;AACH,8BACsB,eAAgB,YAAW,QAAQ;IACpB,SAAS,CAAC,QAAQ,CAAC,kBAAkB,EAAG,yBAAyB,CAAC;IAC1E,SAAS,CAAC,QAAQ,CAAC,gBAAgB,EAAG,gBAAgB,CAAC;IACzD,SAAS,CAAC,QAAQ,CAAC,cAAc,EAAG,cAAc,CAAC;IAC/C,SAAS,CAAC,QAAQ,CAAC,kBAAkB,EAAG,kBAAkB,CAAC;IAChE,QAAQ,CAAC,MAAM,EAAG,aAAa,CAAC;IACvD,mGAAmG;IACxE,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAqB;IAE/E,qFAAqF;IACrF,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAEhD;;;;;OAKG;IACH,SAAS,CAAC,QAAQ,CAAC,yBAAyB,EAAE,OAAO,CAAQ;IAE7D,SAAS,CAAC,QAAQ,CAAC,cAAc,gBAAuB;IACxD,QAAQ,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,CAA6B;IAE5D,SAAS,CAAC,MAAM,CAAC,EAAE,uBAAuB,CAAC;IAC3C,mEAAmE;IACnE,SAAS,CAAC,QAAQ,UAAS;IAE3B,6FAA6F;IAC7F,SAAS,CAAC,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAU;IAC3D,SAAS,CAAC,OAAO,CAAC,EAAE,iBAAiB,CAAC;IACtC,SAAS,CAAC,YAAY,CAAC,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;IACvD,6FAA6F;IAC7F,SAAS,CAAC,kBAAkB,UAAS;IAErC,iFAAiF;IACjF,QAAQ,CAAC,mBAAmB,EAAE,sBAAsB,CAclD;IAEF;;;;;;;;;;;OAWG;IACH,OAAO,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAqBpC;IAED;;;;;;;;;OASG;IACH,WAAW,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,eAAe,GAAG,IAAI,CAS5D;IAED,8DAA8D;IAC9D,SAAS,CAAC,aAAa,CAAC,MAAM,GAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,IAAqC,GAAG,IAAI,CAO3G;IAED,SAAS,KAAK,gBAAgB,IAAI,gBAAgB,CAKjD;IACD,sEAAsE;IACtE,SAAS,CAAC,sBAAsB,CAAC,EAAE,gBAAgB,CAAC;IAEpD,SAAS,CAAC,kBAAkB,IAAI,MAAM,CAErC;IAED,SAAS,CAAC,qBAAqB,IAAI,MAAM,CAMxC;IAED,OAAO,IAAI,IAAI,CAOd;CACH"}
|
|
@@ -0,0 +1,199 @@
|
|
|
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 __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
11
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
12
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
13
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
14
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
15
|
+
};
|
|
16
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
17
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
18
|
+
};
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.ChannelDataPort = void 0;
|
|
21
|
+
const browser_1 = require("@hydranium/client-theia/browser");
|
|
22
|
+
const common_1 = require("@hydranium/client-theia/common");
|
|
23
|
+
const protocol_1 = require("@hydranium/protocol");
|
|
24
|
+
const core_1 = require("@theia/core");
|
|
25
|
+
const service_connection_provider_1 = require("@theia/core/lib/browser/messaging/service-connection-provider");
|
|
26
|
+
const inversify_1 = require("@theia/core/shared/inversify");
|
|
27
|
+
const browser_2 = require("@theia/workspace/lib/browser");
|
|
28
|
+
const channel_connection_1 = require("./channel-connection");
|
|
29
|
+
const workspace_gate_1 = require("./workspace-gate");
|
|
30
|
+
/**
|
|
31
|
+
* A {@link DataPort} over a Theia frontend channel.
|
|
32
|
+
*
|
|
33
|
+
* Subclasses supply {@link servicePath}; everything above the port —
|
|
34
|
+
* `DataConnection`, its sessions, and whatever model a widget drives — is
|
|
35
|
+
* host-neutral and shared with the VS Code and browser shells.
|
|
36
|
+
*
|
|
37
|
+
* Bind one per service path and in singleton scope. Theia keys a frontend
|
|
38
|
+
* channel by its path and refuses a second on a path already open, and the
|
|
39
|
+
* throw escapes the `openChannelConnection` the loser is awaiting, leaving that
|
|
40
|
+
* promise unsettled rather than rejected.
|
|
41
|
+
*/
|
|
42
|
+
let ChannelDataPort = class ChannelDataPort {
|
|
43
|
+
connectionProvider;
|
|
44
|
+
workspaceService;
|
|
45
|
+
messageService;
|
|
46
|
+
connectionReporter;
|
|
47
|
+
logger;
|
|
48
|
+
/** Times the channel's reconnect escalation: the container's {@link Clock}, or a `SystemClock`. */
|
|
49
|
+
clock = new protocol_1.SystemClock();
|
|
50
|
+
/**
|
|
51
|
+
* Re-open the channel when the current connection is lost. Default `true` —
|
|
52
|
+
* re-opening is the only thing that recovers a restarted language server,
|
|
53
|
+
* which binds new ephemeral ports. Turn it off for a frontend that tears
|
|
54
|
+
* itself down on transport loss instead.
|
|
55
|
+
*/
|
|
56
|
+
reconnectOnConnectionLoss = true;
|
|
57
|
+
disposeEmitter = new core_1.Emitter();
|
|
58
|
+
onDispose = this.disposeEmitter.event;
|
|
59
|
+
handle;
|
|
60
|
+
/** Set by {@link dispose}, after which {@link connect} rejects. */
|
|
61
|
+
disposed = false;
|
|
62
|
+
/** How long a connection may take before it is reported as failed; it keeps trying after. */
|
|
63
|
+
connectFailureNoticeMs = 30_000;
|
|
64
|
+
attempt;
|
|
65
|
+
attemptTimer;
|
|
66
|
+
/** Set once {@link connectionLifecycle} reports, which then owns the connection failures. */
|
|
67
|
+
reportsConnections = false;
|
|
68
|
+
/** Reports each connection generation through the {@link ConnectionReporter}. */
|
|
69
|
+
connectionLifecycle = {
|
|
70
|
+
onConnecting: () => {
|
|
71
|
+
this.reportsConnections = true;
|
|
72
|
+
this.settleAttempt();
|
|
73
|
+
const attempt = this.connectionReporter.connecting(this.connectionTarget);
|
|
74
|
+
this.attempt = attempt;
|
|
75
|
+
this.attemptTimer = setTimeout(() => {
|
|
76
|
+
attempt.failed(this.connectTimeoutMessage());
|
|
77
|
+
// The generation keeps waiting; a follow-up attempt reports it connecting.
|
|
78
|
+
this.attempt = this.connectionReporter.connecting(this.connectionTarget);
|
|
79
|
+
}, this.connectFailureNoticeMs);
|
|
80
|
+
},
|
|
81
|
+
onReady: () => this.settleAttempt(attempt => attempt.connected()),
|
|
82
|
+
onFailed: () => this.settleAttempt(attempt => attempt.failed(this.unreachableMessage()))
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Open the workspace-gated channel and hand back its listening connection.
|
|
86
|
+
*
|
|
87
|
+
* Gated because the backend forwarder discovers the head's port by executing
|
|
88
|
+
* a command the language server answers, and the language server only
|
|
89
|
+
* launches once there is a workspace to launch it for. Asking earlier polls
|
|
90
|
+
* a command nobody has registered.
|
|
91
|
+
*
|
|
92
|
+
* Rejects once the port is disposed: the consumer reconnects when the
|
|
93
|
+
* dispose fires {@link onDispose}, and a channel opened then is closed by
|
|
94
|
+
* nothing.
|
|
95
|
+
*/
|
|
96
|
+
connect() {
|
|
97
|
+
if (this.disposed) {
|
|
98
|
+
return Promise.reject(new Error(`${this.constructor.name}: the port is disposed`));
|
|
99
|
+
}
|
|
100
|
+
// Read per call rather than cached: `current` is repointed at a fresh
|
|
101
|
+
// promise every time the channel is re-opened, so reading through the
|
|
102
|
+
// handle is what makes a later generation reach the live server.
|
|
103
|
+
if (!this.handle) {
|
|
104
|
+
this.handle = (0, channel_connection_1.openChannelConnection)(this.connectionProvider, this.servicePath, {
|
|
105
|
+
whenReady: (0, workspace_gate_1.whenWorkspaceOpen)(this.workspaceService),
|
|
106
|
+
logger: this.logger,
|
|
107
|
+
clock: this.clock,
|
|
108
|
+
reconnect: this.reconnectOnConnectionLoss
|
|
109
|
+
});
|
|
110
|
+
// A relaunched server binds new ephemeral ports; the handle re-opens
|
|
111
|
+
// and the forwarder rediscovers, but `DataConnection` caches its
|
|
112
|
+
// generation and would go on addressing the dead one. Its signal to
|
|
113
|
+
// drop that generation is this event.
|
|
114
|
+
this.handle.onDidLoseConnection(() => this.disposeEmitter.fire(undefined));
|
|
115
|
+
}
|
|
116
|
+
return this.handle.current;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Surface a transport or write failure as a Theia notification.
|
|
120
|
+
*
|
|
121
|
+
* Swallowing it is the failure this exists to prevent: a dead connection and
|
|
122
|
+
* an empty document are indistinguishable in a widget.
|
|
123
|
+
*
|
|
124
|
+
* `reported` is already a complete sentence with the detail interpolated, so
|
|
125
|
+
* wrapping it in a sentence of the host's own would nest one owner's clause
|
|
126
|
+
* inside another's and leave no translator in control of the whole.
|
|
127
|
+
*/
|
|
128
|
+
reportError(_error, reported) {
|
|
129
|
+
const connectionFailure = reported.code === protocol_1.DATA_SERVER_CONNECT_FAILED.code || reported.code === protocol_1.DATA_SERVER_NOT_READY.code;
|
|
130
|
+
const message = (0, protocol_1.renderFrameworkMessage)(reported, core_1.nls.localization?.translations);
|
|
131
|
+
if (connectionFailure && this.reportsConnections) {
|
|
132
|
+
// The reporter shows the failure without its detail, so it is kept here.
|
|
133
|
+
this.logger.warn(message);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
this.messageService.error(message);
|
|
137
|
+
}
|
|
138
|
+
/** End the current attempt through `settle`, or cancel it. */
|
|
139
|
+
settleAttempt(settle = attempt => attempt.cancelled()) {
|
|
140
|
+
clearTimeout(this.attemptTimer);
|
|
141
|
+
const attempt = this.attempt;
|
|
142
|
+
this.attempt = undefined;
|
|
143
|
+
if (attempt) {
|
|
144
|
+
settle(attempt);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
get connectionTarget() {
|
|
148
|
+
return (this.cachedConnectionTarget ??= {
|
|
149
|
+
connectingMessage: core_1.nls.localize('hydranium/data-client-theia/data-server-connecting', 'Connecting to the data server…'),
|
|
150
|
+
connectedMessage: core_1.nls.localize('hydranium/data-client-theia/data-server-connected', 'Connected to the data server.')
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
/** One object per port: the reporter keys what it has shown on it. */
|
|
154
|
+
cachedConnectionTarget;
|
|
155
|
+
unreachableMessage() {
|
|
156
|
+
return core_1.nls.localize('hydranium/data-client-theia/data-server-unreachable', 'Could not connect to the data server.');
|
|
157
|
+
}
|
|
158
|
+
connectTimeoutMessage() {
|
|
159
|
+
return core_1.nls.localize('hydranium/data-client-theia/data-server-timeout', 'The data server did not answer within {0} seconds.', String(Math.round(this.connectFailureNoticeMs / 1000)));
|
|
160
|
+
}
|
|
161
|
+
dispose() {
|
|
162
|
+
this.disposed = true;
|
|
163
|
+
this.settleAttempt();
|
|
164
|
+
this.handle?.dispose();
|
|
165
|
+
this.handle = undefined;
|
|
166
|
+
this.disposeEmitter.fire(undefined);
|
|
167
|
+
this.disposeEmitter.dispose();
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
exports.ChannelDataPort = ChannelDataPort;
|
|
171
|
+
__decorate([
|
|
172
|
+
(0, inversify_1.inject)(service_connection_provider_1.RemoteConnectionProvider),
|
|
173
|
+
__metadata("design:type", Function)
|
|
174
|
+
], ChannelDataPort.prototype, "connectionProvider", void 0);
|
|
175
|
+
__decorate([
|
|
176
|
+
(0, inversify_1.inject)(browser_2.WorkspaceService),
|
|
177
|
+
__metadata("design:type", browser_2.WorkspaceService)
|
|
178
|
+
], ChannelDataPort.prototype, "workspaceService", void 0);
|
|
179
|
+
__decorate([
|
|
180
|
+
(0, inversify_1.inject)(core_1.MessageService),
|
|
181
|
+
__metadata("design:type", core_1.MessageService)
|
|
182
|
+
], ChannelDataPort.prototype, "messageService", void 0);
|
|
183
|
+
__decorate([
|
|
184
|
+
(0, inversify_1.inject)(browser_1.ConnectionReporter),
|
|
185
|
+
__metadata("design:type", Object)
|
|
186
|
+
], ChannelDataPort.prototype, "connectionReporter", void 0);
|
|
187
|
+
__decorate([
|
|
188
|
+
(0, inversify_1.inject)(browser_1.ChannelLogger),
|
|
189
|
+
__metadata("design:type", browser_1.ChannelLogger)
|
|
190
|
+
], ChannelDataPort.prototype, "logger", void 0);
|
|
191
|
+
__decorate([
|
|
192
|
+
(0, inversify_1.inject)(common_1.Clock),
|
|
193
|
+
(0, inversify_1.optional)(),
|
|
194
|
+
__metadata("design:type", Object)
|
|
195
|
+
], ChannelDataPort.prototype, "clock", void 0);
|
|
196
|
+
exports.ChannelDataPort = ChannelDataPort = __decorate([
|
|
197
|
+
(0, inversify_1.injectable)()
|
|
198
|
+
], ChannelDataPort);
|
|
199
|
+
//# sourceMappingURL=channel-data-port.js.map
|