@hydranium/data-client-theia 1.0.0-next.23 → 1.0.0-next.230
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 +54 -42
- package/lib/browser/channel-connection.d.ts +6 -6
- package/lib/browser/channel-connection.d.ts.map +1 -1
- package/lib/browser/channel-connection.js +1 -1
- package/lib/browser/channel-connection.js.map +1 -1
- package/lib/browser/channel-data-port.d.ts +83 -0
- package/lib/browser/channel-data-port.d.ts.map +1 -0
- package/lib/browser/channel-data-port.js +180 -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 +10 -10
- 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 +6 -6
- package/src/browser/channel-data-port.ts +174 -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 +11 -14
- 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 -63
- 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 -64
package/README.md
CHANGED
|
@@ -6,10 +6,12 @@ Theia client primitives for the hydranium **data head** — the companion of
|
|
|
6
6
|
|
|
7
7
|
The data head's frontend speaks the data server's own `vscode-jsonrpc` protocol
|
|
8
8
|
over a channel that the Theia backend relays byte-for-byte onto the server's
|
|
9
|
-
socket. This package is
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
socket. This package is the Theia-specific half of that arrangement: the browser-side
|
|
10
|
+
transport and the backend-side connection handler and forwarder. Everything above
|
|
11
|
+
the transport — the connection, its sessions, the event fan-out — is host-neutral
|
|
12
|
+
and lives in `@hydranium/protocol/client`. Install it if a Theia application needs
|
|
13
|
+
form editors, trees, or code-gen driven from the live AST rather than from LSP
|
|
14
|
+
text edits.
|
|
13
15
|
|
|
14
16
|
## What it gives you
|
|
15
17
|
|
|
@@ -22,31 +24,33 @@ code-gen driven from the live AST rather than from LSP text edits.
|
|
|
22
24
|
channel ever arrives on its own. Retries follow `DEFAULT_RECONNECT_DELAYS`,
|
|
23
25
|
escalating per consecutive loss and resetting after
|
|
24
26
|
`RECONNECT_ESCALATION_RESET_MS`.
|
|
25
|
-
- **`
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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.
|
|
27
|
+
- **`ChannelDataPort`** — the `DataPort` implementation over a Theia frontend
|
|
28
|
+
channel, and the only class a Theia adopter has to write against. Subclass it
|
|
29
|
+
with a `servicePath`; it supplies the channel, the workspace gate, the
|
|
30
|
+
reconnect signal and the `MessageService` error sink. Bind one per service path
|
|
31
|
+
in singleton scope. Hand it to `DataConnectionWithEvents` (from
|
|
32
|
+
`@hydranium/protocol`) and each connection reports through the
|
|
33
|
+
`ConnectionReporter`, a data server not ready after 30 s included; the
|
|
34
|
+
connection, its sessions and its event fan-out are the host-neutral ones every
|
|
35
|
+
other shell uses.
|
|
38
36
|
- **`EmitterDataClient`** (on `./common`, not `./browser`) — the default
|
|
39
37
|
client-side implementation of the data protocol's inbound notifications,
|
|
40
|
-
fanning each one out to a Theia `Event
|
|
41
|
-
`
|
|
38
|
+
fanning each one out to a Theia `Event` of the matching name
|
|
39
|
+
(`onDidUpdateDocument`, `onDidSaveDocument`, …). Bind an instance as the
|
|
42
40
|
`localTarget` of the frontend's RPC proxy. It sits on the common tier because
|
|
43
41
|
its only runtime dependency is `@theia/core`'s root entry, which is Theia's own
|
|
44
42
|
common tier, so a backend or a plain-Node consumer can bind it too.
|
|
45
43
|
- **`whenWorkspaceOpen`** — resolves once Theia reports a workspace root. The data
|
|
46
44
|
server only starts once the LSP launches for a workspace, so connecting earlier
|
|
47
45
|
would hang in port discovery; pass this as `whenReady`.
|
|
48
|
-
-
|
|
49
|
-
|
|
46
|
+
- **`DataSessionStopContribution`** — ends a frontend's data sessions with its
|
|
47
|
+
page, so the server ends them as closed rather than waiting for the connection
|
|
48
|
+
to go, and holds the page while one of them is saving.
|
|
49
|
+
`bindDataConnection(bind, isBound, ConnectionClass)` binds a connection with
|
|
50
|
+
its sessions tracked, and binds this contribution and the
|
|
51
|
+
`ConnectionReporter` the connection's port injects unless they are bound.
|
|
52
|
+
- **Backend (`./node`)** — `DataServerConnectionHandler` (the socket bridge,
|
|
53
|
+
over `@hydranium/client-theia`'s `SocketChannelForwarder`),
|
|
50
54
|
`createDataServerConnectionContainerModule(...handlers)` for the
|
|
51
55
|
frontend-scoped module boilerplate, and `HostDiagnosticsServer` with
|
|
52
56
|
`createHostDiagnosticsBackendModule()` — which, paired with the browser-side
|
|
@@ -65,15 +69,11 @@ running hydranium data server to connect to. The declared peer dependencies are:
|
|
|
65
69
|
| Peer | Range |
|
|
66
70
|
| ------------------------- | -------------------- |
|
|
67
71
|
| `@hydranium/client-theia` | `^1.0.0-next` |
|
|
68
|
-
| `@hydranium/core` | `^1.0.0-next` |
|
|
69
72
|
| `@hydranium/protocol` | `^1.0.0-next` |
|
|
70
73
|
| `@theia/core` | `^1.70.0` |
|
|
71
74
|
| `@theia/workspace` | `^1.70.0` |
|
|
72
75
|
| `inversify` | `^6.0.0` |
|
|
73
|
-
| `vscode-jsonrpc` |
|
|
74
|
-
|
|
75
|
-
`@hydranium/core` is reached only from the `./node` tier (the host-diagnostics
|
|
76
|
-
service), so a frontend-only consumer never loads it.
|
|
76
|
+
| `vscode-jsonrpc` | `^9.0.0` |
|
|
77
77
|
|
|
78
78
|
## Wiring
|
|
79
79
|
|
|
@@ -81,17 +81,27 @@ This package declares no `theiaExtensions` — it is a library your own Theia
|
|
|
81
81
|
extension builds on. That extension's `package.json` declares the entries, and
|
|
82
82
|
each entry names one frontend/backend module pair:
|
|
83
83
|
|
|
84
|
-
- the **frontend** module
|
|
85
|
-
|
|
84
|
+
- the **frontend** module:
|
|
85
|
+
- binds your `ChannelDataPort` subclass in singleton scope;
|
|
86
|
+
- calls `bindDataConnection(bind, isBound, MyConnection)` for the connection
|
|
87
|
+
over it;
|
|
88
|
+
- calls `bindChannelLogger` from `@hydranium/client-theia`, where the port
|
|
89
|
+
logs the connection failures it leaves to the reporter, and the connection
|
|
90
|
+
over it logs at debug level what it does on its own;
|
|
91
|
+
- calls `bindEditorDiskSync` from `@hydranium/client-theia`, since a data
|
|
92
|
+
server save writes a file an editor can have open;
|
|
93
|
+
- calls `bindHostDiagnostics` for the diagnostics commands;
|
|
86
94
|
- the **backend** module is typically a one-liner:
|
|
87
95
|
`export default createDataServerConnectionContainerModule(MyHandler)`, where
|
|
88
96
|
`MyHandler` extends `DataServerConnectionHandler`.
|
|
89
97
|
|
|
90
98
|
More than one handler is the normal case, not an exotic one. Theia keys a
|
|
91
99
|
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
|
|
93
|
-
`servicePath` — while the shared `portCommand` still names
|
|
94
|
-
behind them all.
|
|
100
|
+
already open, so every frontend abstraction reaching the data head on its own
|
|
101
|
+
channel needs its own `servicePath` — while the shared `portCommand` still names
|
|
102
|
+
the one server process behind them all. Several participants over ONE channel
|
|
103
|
+
need no second handler: that is what `DataConnection`'s sessions are for, and it
|
|
104
|
+
is the cheaper arrangement.
|
|
95
105
|
|
|
96
106
|
The refusal is silent, which is what makes it expensive: the loser's promise is
|
|
97
107
|
left unsettled rather than rejected, so a frontend that shared a path hangs on
|
|
@@ -103,21 +113,23 @@ its loading state indefinitely with nothing in the server log to say why.
|
|
|
103
113
|
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- |
|
|
104
114
|
| `.` | Nothing — the surface is split by environment, so the root barrel stays empty. | browser-neutral (gated) |
|
|
105
115
|
| `./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`,
|
|
107
|
-
| `./node` | `DataServerConnectionHandler`, `createDataServerConnectionContainerModule`, `
|
|
116
|
+
| `./browser` | `openChannelConnection`, `createChannelConnection`, `ChannelDataPort`, `bindHostDiagnostics`, `whenWorkspaceOpen`, `DataSessionStopContribution`, `bindDataConnection` | browser / Theia frontend (gated) |
|
|
117
|
+
| `./node` | `DataServerConnectionHandler`, `createDataServerConnectionContainerModule`, `HostDiagnosticsServer`, `createHostDiagnosticsBackendModule` | Node / Theia backend |
|
|
108
118
|
|
|
109
|
-
|
|
110
|
-
`
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
119
|
+
Resolve the subpaths with [a resolver that reads
|
|
120
|
+
`exports`](../../docs/adopting/requirements.md#a-resolver-that-reads-exports);
|
|
121
|
+
`"Node"` (node10) reaches none of them. "Gated" means the
|
|
122
|
+
repository's neutral-bundle check enforces that the entry bundles for the
|
|
123
|
+
browser with no `node:*` import, transitive ones included; `./node` is
|
|
124
|
+
deliberately outside that gate. Also 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
125
|
|
|
115
126
|
## Status
|
|
116
127
|
|
|
117
|
-
Alpha — pre-v0,
|
|
118
|
-
deprecation cycle. See
|
|
119
|
-
|
|
120
|
-
[repository README](../../README.md)
|
|
128
|
+
Alpha — pre-v0, published as a `1.0.0-next` prerelease on every merge to `main`.
|
|
129
|
+
The API is not stable and may change without a deprecation cycle. See
|
|
130
|
+
[`docs/concepts/architecture.md`](../../docs/concepts/architecture.md) for the
|
|
131
|
+
data head's place among the heads, and the [repository README](../../README.md)
|
|
132
|
+
for current status and known limitations.
|
|
121
133
|
|
|
122
134
|
## License
|
|
123
135
|
|
|
@@ -21,7 +21,7 @@ import { type Logger, type MessageConnection } from 'vscode-jsonrpc/browser';
|
|
|
21
21
|
* Mirrors `@eclipse-glsp/theia-integration`'s `createChannelConnection`, but
|
|
22
22
|
* lives here so the data-server head carries no GLSP dependency.
|
|
23
23
|
*/
|
|
24
|
-
export declare function createChannelConnection(channel: Channel, logger
|
|
24
|
+
export declare function createChannelConnection(channel: Channel, logger: Logger): MessageConnection;
|
|
25
25
|
/** Options for {@link openChannelConnection}. */
|
|
26
26
|
export interface OpenChannelConnectionOptions {
|
|
27
27
|
/**
|
|
@@ -32,8 +32,8 @@ export interface OpenChannelConnectionOptions {
|
|
|
32
32
|
* publishes the port) exists.
|
|
33
33
|
*/
|
|
34
34
|
whenReady?: Promise<void>;
|
|
35
|
-
/**
|
|
36
|
-
logger
|
|
35
|
+
/** Where the connection logs its protocol faults; without one they leave no trace. */
|
|
36
|
+
logger: Logger;
|
|
37
37
|
/**
|
|
38
38
|
* Re-establish the connection whenever the live one is lost, by RE-OPENING
|
|
39
39
|
* the channel on `path` — not merely by rebuilding over a channel Theia hands
|
|
@@ -107,8 +107,8 @@ export interface ChannelConnectionHandle {
|
|
|
107
107
|
/**
|
|
108
108
|
* The current connection. Read it per use rather than caching it: after a
|
|
109
109
|
* reconnect this returns a NEW promise, and a cached one resolves to the
|
|
110
|
-
* dead connection forever. The same reasoning makes
|
|
111
|
-
*
|
|
110
|
+
* dead connection forever. The same reasoning makes `RpcConnection` rebuild
|
|
111
|
+
* its proxy per generation rather than hold one.
|
|
112
112
|
*/
|
|
113
113
|
readonly current: Promise<MessageConnection>;
|
|
114
114
|
/**
|
|
@@ -170,5 +170,5 @@ export interface ChannelConnectionHandle {
|
|
|
170
170
|
* so the consumer's request is left unsettled: a hang with a clean log, not an
|
|
171
171
|
* error. Owning the re-open here means owning it exclusively.
|
|
172
172
|
*/
|
|
173
|
-
export declare function openChannelConnection(connectionProvider: ServiceConnectionProvider, path: string, options
|
|
173
|
+
export declare function openChannelConnection(connectionProvider: ServiceConnectionProvider, path: string, options: OpenChannelConnectionOptions): ChannelConnectionHandle;
|
|
174
174
|
//# 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,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;;;;;;;;;;;;;;;;;;;;;;;;;;;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,CA4IzB"}
|
|
@@ -126,7 +126,7 @@ exports.RECONNECT_ESCALATION_RESET_MS = 30_000;
|
|
|
126
126
|
* so the consumer's request is left unsettled: a hang with a clean log, not an
|
|
127
127
|
* error. Owning the re-open here means owning it exclusively.
|
|
128
128
|
*/
|
|
129
|
-
function openChannelConnection(connectionProvider, path, options
|
|
129
|
+
function openChannelConnection(connectionProvider, path, options) {
|
|
130
130
|
const reconnect = options.reconnect ?? true;
|
|
131
131
|
const reconnectDelays = options.reconnectDelays ?? exports.DEFAULT_RECONNECT_DELAYS;
|
|
132
132
|
const reconnectEmitter = new core_1.Emitter();
|
|
@@ -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,83 @@
|
|
|
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 { type DataPort, type ResolvedMessage, type RpcConnectionLifecycle } from '@hydranium/protocol';
|
|
11
|
+
import { Emitter, MessageService, type Event } from '@theia/core';
|
|
12
|
+
import { type ServiceConnectionProvider } from '@theia/core/lib/browser';
|
|
13
|
+
import { WorkspaceService } from '@theia/workspace/lib/browser';
|
|
14
|
+
import type { MessageConnection } from 'vscode-jsonrpc';
|
|
15
|
+
import { type ChannelConnectionHandle } from './channel-connection';
|
|
16
|
+
/**
|
|
17
|
+
* A {@link DataPort} over a Theia frontend channel.
|
|
18
|
+
*
|
|
19
|
+
* Subclasses supply {@link servicePath}; everything above the port —
|
|
20
|
+
* `DataConnection`, its sessions, and whatever model a widget drives — is
|
|
21
|
+
* host-neutral and shared with the VS Code and browser shells.
|
|
22
|
+
*
|
|
23
|
+
* Bind one per service path and in singleton scope. Theia keys a frontend
|
|
24
|
+
* channel by its path and refuses a second on a path already open, and the
|
|
25
|
+
* throw escapes the `openChannelConnection` the loser is awaiting, leaving that
|
|
26
|
+
* promise unsettled rather than rejected.
|
|
27
|
+
*/
|
|
28
|
+
export declare abstract class ChannelDataPort implements DataPort {
|
|
29
|
+
protected readonly connectionProvider: ServiceConnectionProvider;
|
|
30
|
+
protected readonly workspaceService: WorkspaceService;
|
|
31
|
+
protected readonly messageService: MessageService;
|
|
32
|
+
protected readonly connectionReporter: ConnectionReporter;
|
|
33
|
+
readonly logger: ChannelLogger;
|
|
34
|
+
/** Frontend service path the backend forwarder for this head is registered under. */
|
|
35
|
+
protected abstract readonly servicePath: string;
|
|
36
|
+
/**
|
|
37
|
+
* Re-open the channel when the current connection is lost. Default `true` —
|
|
38
|
+
* re-opening is the only thing that recovers a restarted language server,
|
|
39
|
+
* which binds new ephemeral ports. Turn it off for a frontend that tears
|
|
40
|
+
* itself down on transport loss instead.
|
|
41
|
+
*/
|
|
42
|
+
protected readonly reconnectOnConnectionLoss: boolean;
|
|
43
|
+
protected readonly disposeEmitter: Emitter<void>;
|
|
44
|
+
readonly onDispose: Event<void>;
|
|
45
|
+
protected handle?: ChannelConnectionHandle;
|
|
46
|
+
/** How long a connection may take before it is reported as failed; it keeps trying after. */
|
|
47
|
+
protected readonly connectFailureNoticeMs: number;
|
|
48
|
+
protected attempt?: ConnectionAttempt;
|
|
49
|
+
protected attemptTimer?: ReturnType<typeof setTimeout>;
|
|
50
|
+
/** Set once {@link connectionLifecycle} reports, which then owns the connection failures. */
|
|
51
|
+
protected reportsConnections: boolean;
|
|
52
|
+
/** Reports each connection generation through the {@link ConnectionReporter}. */
|
|
53
|
+
readonly connectionLifecycle: RpcConnectionLifecycle;
|
|
54
|
+
/**
|
|
55
|
+
* Open the workspace-gated channel and hand back its listening connection.
|
|
56
|
+
*
|
|
57
|
+
* Gated because the backend forwarder discovers the head's port by executing
|
|
58
|
+
* a command the language server answers, and the language server only
|
|
59
|
+
* launches once there is a workspace to launch it for. Asking earlier polls
|
|
60
|
+
* a command nobody has registered.
|
|
61
|
+
*/
|
|
62
|
+
connect(): Promise<MessageConnection>;
|
|
63
|
+
/**
|
|
64
|
+
* Surface a transport or write failure as a Theia notification.
|
|
65
|
+
*
|
|
66
|
+
* Swallowing it is the failure this exists to prevent: a dead connection and
|
|
67
|
+
* an empty document are indistinguishable in a widget.
|
|
68
|
+
*
|
|
69
|
+
* `reported` is already a complete sentence with the detail interpolated, so
|
|
70
|
+
* wrapping it in a sentence of the host's own would nest one owner's clause
|
|
71
|
+
* inside another's and leave no translator in control of the whole.
|
|
72
|
+
*/
|
|
73
|
+
reportError(_error: unknown, reported: ResolvedMessage): void;
|
|
74
|
+
/** End the current attempt through `settle`, or cancel it. */
|
|
75
|
+
protected settleAttempt(settle?: (attempt: ConnectionAttempt) => void): void;
|
|
76
|
+
protected get connectionTarget(): ConnectionTarget;
|
|
77
|
+
/** One object per port: the reporter keys what it has shown on it. */
|
|
78
|
+
protected cachedConnectionTarget?: ConnectionTarget;
|
|
79
|
+
protected unreachableMessage(): string;
|
|
80
|
+
protected connectTimeoutMessage(): string;
|
|
81
|
+
dispose(): void;
|
|
82
|
+
}
|
|
83
|
+
//# 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,EAIJ,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;IAEvD,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;IAE3C,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;;;;;;;OAOG;IACH,OAAO,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAiBpC;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,CAMd;CACH"}
|
|
@@ -0,0 +1,180 @@
|
|
|
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 protocol_1 = require("@hydranium/protocol");
|
|
23
|
+
const core_1 = require("@theia/core");
|
|
24
|
+
const service_connection_provider_1 = require("@theia/core/lib/browser/messaging/service-connection-provider");
|
|
25
|
+
const inversify_1 = require("@theia/core/shared/inversify");
|
|
26
|
+
const browser_2 = require("@theia/workspace/lib/browser");
|
|
27
|
+
const channel_connection_1 = require("./channel-connection");
|
|
28
|
+
const workspace_gate_1 = require("./workspace-gate");
|
|
29
|
+
/**
|
|
30
|
+
* A {@link DataPort} over a Theia frontend channel.
|
|
31
|
+
*
|
|
32
|
+
* Subclasses supply {@link servicePath}; everything above the port —
|
|
33
|
+
* `DataConnection`, its sessions, and whatever model a widget drives — is
|
|
34
|
+
* host-neutral and shared with the VS Code and browser shells.
|
|
35
|
+
*
|
|
36
|
+
* Bind one per service path and in singleton scope. Theia keys a frontend
|
|
37
|
+
* channel by its path and refuses a second on a path already open, and the
|
|
38
|
+
* throw escapes the `openChannelConnection` the loser is awaiting, leaving that
|
|
39
|
+
* promise unsettled rather than rejected.
|
|
40
|
+
*/
|
|
41
|
+
let ChannelDataPort = class ChannelDataPort {
|
|
42
|
+
connectionProvider;
|
|
43
|
+
workspaceService;
|
|
44
|
+
messageService;
|
|
45
|
+
connectionReporter;
|
|
46
|
+
logger;
|
|
47
|
+
/**
|
|
48
|
+
* Re-open the channel when the current connection is lost. Default `true` —
|
|
49
|
+
* re-opening is the only thing that recovers a restarted language server,
|
|
50
|
+
* which binds new ephemeral ports. Turn it off for a frontend that tears
|
|
51
|
+
* itself down on transport loss instead.
|
|
52
|
+
*/
|
|
53
|
+
reconnectOnConnectionLoss = true;
|
|
54
|
+
disposeEmitter = new core_1.Emitter();
|
|
55
|
+
onDispose = this.disposeEmitter.event;
|
|
56
|
+
handle;
|
|
57
|
+
/** How long a connection may take before it is reported as failed; it keeps trying after. */
|
|
58
|
+
connectFailureNoticeMs = 30_000;
|
|
59
|
+
attempt;
|
|
60
|
+
attemptTimer;
|
|
61
|
+
/** Set once {@link connectionLifecycle} reports, which then owns the connection failures. */
|
|
62
|
+
reportsConnections = false;
|
|
63
|
+
/** Reports each connection generation through the {@link ConnectionReporter}. */
|
|
64
|
+
connectionLifecycle = {
|
|
65
|
+
onConnecting: () => {
|
|
66
|
+
this.reportsConnections = true;
|
|
67
|
+
this.settleAttempt();
|
|
68
|
+
const attempt = this.connectionReporter.connecting(this.connectionTarget);
|
|
69
|
+
this.attempt = attempt;
|
|
70
|
+
this.attemptTimer = setTimeout(() => {
|
|
71
|
+
attempt.failed(this.connectTimeoutMessage());
|
|
72
|
+
// The generation keeps waiting; a follow-up attempt reports it connecting.
|
|
73
|
+
this.attempt = this.connectionReporter.connecting(this.connectionTarget);
|
|
74
|
+
}, this.connectFailureNoticeMs);
|
|
75
|
+
},
|
|
76
|
+
onReady: () => this.settleAttempt(attempt => attempt.connected()),
|
|
77
|
+
onFailed: () => this.settleAttempt(attempt => attempt.failed(this.unreachableMessage()))
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Open the workspace-gated channel and hand back its listening connection.
|
|
81
|
+
*
|
|
82
|
+
* Gated because the backend forwarder discovers the head's port by executing
|
|
83
|
+
* a command the language server answers, and the language server only
|
|
84
|
+
* launches once there is a workspace to launch it for. Asking earlier polls
|
|
85
|
+
* a command nobody has registered.
|
|
86
|
+
*/
|
|
87
|
+
connect() {
|
|
88
|
+
// Read per call rather than cached: `current` is repointed at a fresh
|
|
89
|
+
// promise every time the channel is re-opened, so reading through the
|
|
90
|
+
// handle is what makes a later generation reach the live server.
|
|
91
|
+
if (!this.handle) {
|
|
92
|
+
this.handle = (0, channel_connection_1.openChannelConnection)(this.connectionProvider, this.servicePath, {
|
|
93
|
+
whenReady: (0, workspace_gate_1.whenWorkspaceOpen)(this.workspaceService),
|
|
94
|
+
logger: this.logger,
|
|
95
|
+
reconnect: this.reconnectOnConnectionLoss
|
|
96
|
+
});
|
|
97
|
+
// A relaunched server binds new ephemeral ports; the handle re-opens
|
|
98
|
+
// and the forwarder rediscovers, but `DataConnection` caches its
|
|
99
|
+
// generation and would go on addressing the dead one. Its signal to
|
|
100
|
+
// drop that generation is this event.
|
|
101
|
+
this.handle.onDidLoseConnection(() => this.disposeEmitter.fire(undefined));
|
|
102
|
+
}
|
|
103
|
+
return this.handle.current;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Surface a transport or write failure as a Theia notification.
|
|
107
|
+
*
|
|
108
|
+
* Swallowing it is the failure this exists to prevent: a dead connection and
|
|
109
|
+
* an empty document are indistinguishable in a widget.
|
|
110
|
+
*
|
|
111
|
+
* `reported` is already a complete sentence with the detail interpolated, so
|
|
112
|
+
* wrapping it in a sentence of the host's own would nest one owner's clause
|
|
113
|
+
* inside another's and leave no translator in control of the whole.
|
|
114
|
+
*/
|
|
115
|
+
reportError(_error, reported) {
|
|
116
|
+
const connectionFailure = reported.code === protocol_1.DATA_SERVER_CONNECT_FAILED.code || reported.code === protocol_1.DATA_SERVER_NOT_READY.code;
|
|
117
|
+
const message = (0, protocol_1.renderFrameworkMessage)(reported, core_1.nls.localization?.translations);
|
|
118
|
+
if (connectionFailure && this.reportsConnections) {
|
|
119
|
+
// The reporter shows the failure without its detail, so it is kept here.
|
|
120
|
+
this.logger.warn(message);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
this.messageService.error(message);
|
|
124
|
+
}
|
|
125
|
+
/** End the current attempt through `settle`, or cancel it. */
|
|
126
|
+
settleAttempt(settle = attempt => attempt.cancelled()) {
|
|
127
|
+
clearTimeout(this.attemptTimer);
|
|
128
|
+
const attempt = this.attempt;
|
|
129
|
+
this.attempt = undefined;
|
|
130
|
+
if (attempt) {
|
|
131
|
+
settle(attempt);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
get connectionTarget() {
|
|
135
|
+
return (this.cachedConnectionTarget ??= {
|
|
136
|
+
connectingMessage: core_1.nls.localize('hydranium/data-client-theia/data-server-connecting', 'Connecting to the data server…'),
|
|
137
|
+
connectedMessage: core_1.nls.localize('hydranium/data-client-theia/data-server-connected', 'Connected to the data server.')
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
/** One object per port: the reporter keys what it has shown on it. */
|
|
141
|
+
cachedConnectionTarget;
|
|
142
|
+
unreachableMessage() {
|
|
143
|
+
return core_1.nls.localize('hydranium/data-client-theia/data-server-unreachable', 'Could not connect to the data server.');
|
|
144
|
+
}
|
|
145
|
+
connectTimeoutMessage() {
|
|
146
|
+
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)));
|
|
147
|
+
}
|
|
148
|
+
dispose() {
|
|
149
|
+
this.settleAttempt();
|
|
150
|
+
this.handle?.dispose();
|
|
151
|
+
this.handle = undefined;
|
|
152
|
+
this.disposeEmitter.fire(undefined);
|
|
153
|
+
this.disposeEmitter.dispose();
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
exports.ChannelDataPort = ChannelDataPort;
|
|
157
|
+
__decorate([
|
|
158
|
+
(0, inversify_1.inject)(service_connection_provider_1.RemoteConnectionProvider),
|
|
159
|
+
__metadata("design:type", Function)
|
|
160
|
+
], ChannelDataPort.prototype, "connectionProvider", void 0);
|
|
161
|
+
__decorate([
|
|
162
|
+
(0, inversify_1.inject)(browser_2.WorkspaceService),
|
|
163
|
+
__metadata("design:type", browser_2.WorkspaceService)
|
|
164
|
+
], ChannelDataPort.prototype, "workspaceService", void 0);
|
|
165
|
+
__decorate([
|
|
166
|
+
(0, inversify_1.inject)(core_1.MessageService),
|
|
167
|
+
__metadata("design:type", core_1.MessageService)
|
|
168
|
+
], ChannelDataPort.prototype, "messageService", void 0);
|
|
169
|
+
__decorate([
|
|
170
|
+
(0, inversify_1.inject)(browser_1.ConnectionReporter),
|
|
171
|
+
__metadata("design:type", Object)
|
|
172
|
+
], ChannelDataPort.prototype, "connectionReporter", void 0);
|
|
173
|
+
__decorate([
|
|
174
|
+
(0, inversify_1.inject)(browser_1.ChannelLogger),
|
|
175
|
+
__metadata("design:type", browser_1.ChannelLogger)
|
|
176
|
+
], ChannelDataPort.prototype, "logger", void 0);
|
|
177
|
+
exports.ChannelDataPort = ChannelDataPort = __decorate([
|
|
178
|
+
(0, inversify_1.injectable)()
|
|
179
|
+
], ChannelDataPort);
|
|
180
|
+
//# sourceMappingURL=channel-data-port.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel-data-port.js","sourceRoot":"","sources":["../../src/browser/channel-data-port.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,76 @@
|
|
|
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 { DataConnection, DataServerProtocol, DataSession, DiagnosticOf, TransferElement } from '@hydranium/protocol';
|
|
10
|
+
import { FrontendApplicationContribution, type OnWillStopAction } from '@theia/core/lib/browser/frontend-application-contribution';
|
|
11
|
+
import { type interfaces } from '@theia/core/shared/inversify';
|
|
12
|
+
/**
|
|
13
|
+
* Ends a Theia frontend's data sessions with its page, and holds the page
|
|
14
|
+
* while one of them is still saving. {@link bindDataConnection} binds it and
|
|
15
|
+
* has it {@link track} each data connection bound through it.
|
|
16
|
+
*
|
|
17
|
+
* **Stop.** {@link onStop} disposes every tracked session, which sends its
|
|
18
|
+
* `closeSession`, so the server ends it as closed: each document it was the
|
|
19
|
+
* last to hold is released at once. Otherwise the server learns of the page only
|
|
20
|
+
* when its connection goes, which Theia may hold open for its reconnect
|
|
21
|
+
* timeout, and then ends the sessions as lost, so their documents wait out the
|
|
22
|
+
* release grace. The close is best effort: under load the page's last frames
|
|
23
|
+
* can be lost on the way, and the server then ends the sessions as lost. A
|
|
24
|
+
* session with calls still in flight at the stop closes after them, which is
|
|
25
|
+
* too late for a page going away, and is left to the server's connection-close
|
|
26
|
+
* cleanup. A call a session makes in the same tick as the stop is refused and
|
|
27
|
+
* never sent.
|
|
28
|
+
*
|
|
29
|
+
* **Veto.** While a tracked session has a save in flight, {@link onWillStop}
|
|
30
|
+
* vetoes, including a session disposed meanwhile. In Electron, Theia awaits
|
|
31
|
+
* the veto's action, which waits for the saves to answer, a save started during
|
|
32
|
+
* the wait included, up to {@link maxSettlePasses} times. The browser runs no
|
|
33
|
+
* action before unload, so there the veto shows the leave-page prompt and
|
|
34
|
+
* nothing more. Theia ignores every veto under
|
|
35
|
+
* `application.confirmExit: 'never'`, so there a save in flight at the stop
|
|
36
|
+
* may be cut off.
|
|
37
|
+
*/
|
|
38
|
+
export declare class DataSessionStopContribution implements FrontendApplicationContribution {
|
|
39
|
+
/** The sessions to end at the stop, and to wait for while they save. */
|
|
40
|
+
protected readonly sessions: Set<DataSession<TransferElement, DataServerProtocol<TransferElement, import("@hydranium/protocol").TransferDiagnostic, import("@hydranium/protocol").Project>>>;
|
|
41
|
+
/**
|
|
42
|
+
* How many times the veto's action waits for the saves in flight before it
|
|
43
|
+
* lets the stop go. Each wait ends by the session's own bound, so a save
|
|
44
|
+
* that never answers holds the stop for a few bounds, not for good.
|
|
45
|
+
*/
|
|
46
|
+
protected readonly maxSettlePasses: number;
|
|
47
|
+
/**
|
|
48
|
+
* Take every session of `connection` into the stop: the ones it has
|
|
49
|
+
* started already, and each one it starts from now on. Generic, so a
|
|
50
|
+
* connection of any transfer root, server and client fits.
|
|
51
|
+
*/
|
|
52
|
+
track<TTransfer extends TransferElement, TServer extends DataServerProtocol<TTransfer, DiagnosticOf<TServer>>, TClient extends object>(connection: DataConnection<TTransfer, TServer, TClient>): void;
|
|
53
|
+
/**
|
|
54
|
+
* Take `session` into the stop. It leaves once it has ended and its saves
|
|
55
|
+
* have answered, so a session disposed with a save in flight still holds
|
|
56
|
+
* the page.
|
|
57
|
+
*/
|
|
58
|
+
protected trackSession(session: DataSession<TransferElement>): void;
|
|
59
|
+
onWillStop(): OnWillStopAction | undefined;
|
|
60
|
+
/** The tracked sessions with a save in flight. */
|
|
61
|
+
protected savingSessions(): DataSession<TransferElement>[];
|
|
62
|
+
onStop(): void;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Bind `connectionClass` in singleton scope, its sessions tracked by the
|
|
66
|
+
* {@link DataSessionStopContribution}, and bind that contribution and the
|
|
67
|
+
* `ConnectionReporter` the connection's port injects unless they are bound
|
|
68
|
+
* already. The binding's `onActivation` is taken; a frontend's own hook on the
|
|
69
|
+
* connection goes on the container's `onActivation`, which runs after it. A
|
|
70
|
+
* subclass of the contribution is rebound after this call: one bound before it
|
|
71
|
+
* is never registered as a frontend contribution, so its stop never runs.
|
|
72
|
+
* Call it once per connection class: a second call makes the class's lookup
|
|
73
|
+
* ambiguous.
|
|
74
|
+
*/
|
|
75
|
+
export declare function bindDataConnection<TTransfer extends TransferElement, TServer extends DataServerProtocol<TTransfer, DiagnosticOf<TServer>>, TClient extends object>(bind: interfaces.Bind, isBound: interfaces.IsBound, connectionClass: interfaces.Newable<DataConnection<TTransfer, TServer, TClient>>): void;
|
|
76
|
+
//# sourceMappingURL=data-session-stop-contribution.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-session-stop-contribution.d.ts","sourceRoot":"","sources":["../../src/browser/data-session-stop-contribution.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAGlF,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,WAAW,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAG1H,OAAO,EAAE,+BAA+B,EAAE,KAAK,gBAAgB,EAAE,MAAM,2DAA2D,CAAC;AACnI,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAE3E;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBACa,2BAA4B,YAAW,+BAA+B;IAChF,wEAAwE;IACxE,SAAS,CAAC,QAAQ,CAAC,QAAQ,kKAA2C;IACtE;;;;OAIG;IACH,SAAS,CAAC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAK;IAE/C;;;;OAIG;IACH,KAAK,CAAC,SAAS,SAAS,eAAe,EAAE,OAAO,SAAS,kBAAkB,CAAC,SAAS,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC,EAAE,OAAO,SAAS,MAAM,EAClI,UAAU,EAAE,cAAc,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC,GACvD,IAAI,CAGN;IAED;;;;OAIG;IACH,SAAS,CAAC,YAAY,CAAC,OAAO,EAAE,WAAW,CAAC,eAAe,CAAC,GAAG,IAAI,CAYlE;IAED,UAAU,IAAI,gBAAgB,GAAG,SAAS,CAoBzC;IAED,kDAAkD;IAClD,SAAS,CAAC,cAAc,IAAI,WAAW,CAAC,eAAe,CAAC,EAAE,CAEzD;IAED,MAAM,IAAI,IAAI,CAIb;CACH;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAC/B,SAAS,SAAS,eAAe,EACjC,OAAO,SAAS,kBAAkB,CAAC,SAAS,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC,EACpE,OAAO,SAAS,MAAM,EAEtB,IAAI,EAAE,UAAU,CAAC,IAAI,EACrB,OAAO,EAAE,UAAU,CAAC,OAAO,EAC3B,eAAe,EAAE,UAAU,CAAC,OAAO,CAAC,cAAc,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,GAChF,IAAI,CAaN"}
|