@c8y/ngx-components 1024.2.4 → 1024.5.1

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.
Files changed (56) hide show
  1. package/fesm2022/c8y-ngx-components-bookmarks.mjs +33 -23
  2. package/fesm2022/c8y-ngx-components-bookmarks.mjs.map +1 -1
  3. package/fesm2022/c8y-ngx-components-context-dashboard.mjs +2 -2
  4. package/fesm2022/c8y-ngx-components-context-dashboard.mjs.map +1 -1
  5. package/fesm2022/c8y-ngx-components-datapoint-explorer-view.mjs +3 -2
  6. package/fesm2022/c8y-ngx-components-datapoint-explorer-view.mjs.map +1 -1
  7. package/fesm2022/c8y-ngx-components-device-profile.mjs +27 -9
  8. package/fesm2022/c8y-ngx-components-device-profile.mjs.map +1 -1
  9. package/fesm2022/c8y-ngx-components-protocol-opcua.mjs +9 -11
  10. package/fesm2022/c8y-ngx-components-protocol-opcua.mjs.map +1 -1
  11. package/fesm2022/c8y-ngx-components-remote-access-data.mjs +43 -0
  12. package/fesm2022/c8y-ngx-components-remote-access-data.mjs.map +1 -1
  13. package/fesm2022/c8y-ngx-components-remote-access-ssh-remote-access-ssh-endpoint-modal.mjs +17 -3
  14. package/fesm2022/c8y-ngx-components-remote-access-ssh-remote-access-ssh-endpoint-modal.mjs.map +1 -1
  15. package/fesm2022/c8y-ngx-components-remote-access-terminal-viewer.mjs +179 -41
  16. package/fesm2022/c8y-ngx-components-remote-access-terminal-viewer.mjs.map +1 -1
  17. package/fesm2022/c8y-ngx-components-repository-firmware.mjs +5 -4
  18. package/fesm2022/c8y-ngx-components-repository-firmware.mjs.map +1 -1
  19. package/fesm2022/c8y-ngx-components-repository-software.mjs +10 -4
  20. package/fesm2022/c8y-ngx-components-repository-software.mjs.map +1 -1
  21. package/fesm2022/c8y-ngx-components-trusted-certificates.mjs +5 -4
  22. package/fesm2022/c8y-ngx-components-trusted-certificates.mjs.map +1 -1
  23. package/fesm2022/c8y-ngx-components-widgets-implementations-asset-table.mjs +6 -5
  24. package/fesm2022/c8y-ngx-components-widgets-implementations-asset-table.mjs.map +1 -1
  25. package/fesm2022/c8y-ngx-components.mjs +9 -4
  26. package/fesm2022/c8y-ngx-components.mjs.map +1 -1
  27. package/locales/de.po +61 -5
  28. package/locales/es.po +61 -5
  29. package/locales/fr.po +61 -5
  30. package/locales/ja_JP.po +60 -6
  31. package/locales/ko.po +61 -5
  32. package/locales/locales.pot +60 -4
  33. package/locales/nl.po +61 -5
  34. package/locales/pl.po +61 -5
  35. package/locales/pt_BR.po +61 -5
  36. package/locales/zh_CN.po +61 -5
  37. package/locales/zh_TW.po +61 -5
  38. package/package.json +1 -1
  39. package/types/c8y-ngx-components-bookmarks.d.ts +7 -7
  40. package/types/c8y-ngx-components-bookmarks.d.ts.map +1 -1
  41. package/types/c8y-ngx-components-device-profile.d.ts +5 -2
  42. package/types/c8y-ngx-components-device-profile.d.ts.map +1 -1
  43. package/types/c8y-ngx-components-protocol-opcua.d.ts.map +1 -1
  44. package/types/c8y-ngx-components-remote-access-data.d.ts +19 -0
  45. package/types/c8y-ngx-components-remote-access-data.d.ts.map +1 -1
  46. package/types/c8y-ngx-components-remote-access-ssh-remote-access-ssh-endpoint-modal.d.ts +10 -3
  47. package/types/c8y-ngx-components-remote-access-ssh-remote-access-ssh-endpoint-modal.d.ts.map +1 -1
  48. package/types/c8y-ngx-components-remote-access-terminal-viewer.d.ts +20 -2
  49. package/types/c8y-ngx-components-remote-access-terminal-viewer.d.ts.map +1 -1
  50. package/types/c8y-ngx-components-repository-firmware.d.ts.map +1 -1
  51. package/types/c8y-ngx-components-repository-software.d.ts +2 -0
  52. package/types/c8y-ngx-components-repository-software.d.ts.map +1 -1
  53. package/types/c8y-ngx-components-trusted-certificates.d.ts.map +1 -1
  54. package/types/c8y-ngx-components-widgets-implementations-asset-table.d.ts.map +1 -1
  55. package/types/c8y-ngx-components.d.ts +5 -1
  56. package/types/c8y-ngx-components.d.ts.map +1 -1
@@ -1,6 +1,9 @@
1
1
  import * as i0 from '@angular/core';
2
- import { ViewEncapsulation, Component } from '@angular/core';
3
- import { TitleComponent, ActionBarItemComponent, IconDirective, C8yTranslatePipe } from '@c8y/ngx-components';
2
+ import { signal, ChangeDetectionStrategy, ViewEncapsulation, Component } from '@angular/core';
3
+ import * as i3 from '@c8y/ngx-components';
4
+ import { Status, TitleComponent, ActionBarItemComponent, IconDirective, C8yTranslatePipe } from '@c8y/ngx-components';
5
+ import { gettext } from '@c8y/ngx-components/gettext';
6
+ import * as i4 from '@ngx-translate/core';
4
7
  import { Terminal } from '@xterm/xterm';
5
8
  import { FitAddon } from '@xterm/addon-fit';
6
9
  import * as i1 from '@c8y/ngx-components/remote-access/data';
@@ -137,27 +140,74 @@ class ShellAdapter {
137
140
  }
138
141
  }
139
142
 
143
+ /**
144
+ * Wire model for the Cloud Remote Access first-use host-key confirmation flow (DM-6421).
145
+ *
146
+ * The remote-access WebSocket normally carries a raw telnet/binary shell stream. When the backend
147
+ * runs a host-key probe (first use, `hostkey-autosave=false`, no key stored yet) it prepends a single
148
+ * JSON control frame — delivered as a binary WebSocket frame — and then tears the socket down. The UI
149
+ * distinguishes such a control frame from terminal bytes via the `type` discriminator.
150
+ */
151
+ /**
152
+ * Attempts to parse an incoming WebSocket frame as a remote-access control frame. Returns the parsed
153
+ * frame for a recognised `type`, or `null` when the data is a raw terminal byte stream (the normal
154
+ * case) — the caller then falls through to the telnet/xterm path. Never throws.
155
+ */
156
+ function parseControlFrame(data) {
157
+ if (data === null || typeof data !== 'object') {
158
+ return null;
159
+ }
160
+ let bytes;
161
+ try {
162
+ // The socket uses binaryType='arraybuffer', so frames arrive as ArrayBuffer. Constructing a
163
+ // Uint8Array works across JS realms, unlike `instanceof ArrayBuffer` (which breaks under jsdom).
164
+ bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
165
+ }
166
+ catch {
167
+ return null;
168
+ }
169
+ // Control frames are JSON objects; a raw shell stream almost never starts with '{' (0x7b), so this
170
+ // cheaply skips the JSON.parse attempt for terminal data.
171
+ if (bytes.length === 0 || bytes[0] !== 0x7b) {
172
+ return null;
173
+ }
174
+ try {
175
+ const parsed = JSON.parse(new TextDecoder().decode(bytes));
176
+ if (parsed?.type === 'hostkey' || parsed?.type === 'error') {
177
+ return parsed;
178
+ }
179
+ }
180
+ catch {
181
+ // Not JSON — raw terminal data that happened to start with '{'. Fall through to the terminal path.
182
+ }
183
+ return null;
184
+ }
185
+ /** Composes the OpenSSH authorized_keys-style string persisted in the configuration credentials. */
186
+ function toStoredHostKey(frame) {
187
+ return `${frame.keyType} ${frame.publicKey}`;
188
+ }
189
+
140
190
  class TerminalViewerComponent {
141
- constructor(remoteAccess, activatedRoute) {
191
+ constructor(remoteAccess, activatedRoute, modalService, alertService, translateService) {
142
192
  this.remoteAccess = remoteAccess;
143
193
  this.activatedRoute = activatedRoute;
194
+ this.modalService = modalService;
195
+ this.alertService = alertService;
196
+ this.translateService = translateService;
144
197
  this.title = '';
145
198
  this.container = null;
146
199
  this.terminal = null;
147
200
  this.socket = null;
148
- this.firstDeviceMessageReceived = false;
201
+ /** True once real terminal bytes start flowing (not while a host-key control frame is handled). */
202
+ this.firstDeviceMessageReceived = signal(false, ...(ngDevMode ? [{ debugName: "firstDeviceMessageReceived" }] : /* istanbul ignore next */ []));
203
+ /** Suppresses the "device disconnected" banner while a probe socket is torn down for confirmation. */
204
+ this.awaitingHostKeyConfirmation = false;
149
205
  this.configurationId = this.activatedRoute.snapshot.params.configurationId;
150
206
  this.deviceId = this.activatedRoute.parent.snapshot.params.id;
151
207
  }
152
208
  ngOnDestroy() {
153
209
  this.observer?.disconnect();
154
- const stringToSend = 'exit\n';
155
- const sendQueue = [];
156
- for (let i = 0; i < stringToSend.length; i++) {
157
- sendQueue.push(stringToSend.charCodeAt(i));
158
- }
159
- this.socket?.send(new Uint8Array(sendQueue));
160
- this.socket?.close();
210
+ this.closeSocket();
161
211
  }
162
212
  ngAfterViewInit() {
163
213
  this.container = document.getElementById('terminal-screen');
@@ -167,32 +217,63 @@ class TerminalViewerComponent {
167
217
  cursorBlink: true
168
218
  });
169
219
  const fitAddon = new FitAddon();
170
- this.socket = new WebSocket(this.remoteAccess.getWebSocketUri(this.deviceId, this.configurationId), 'binary');
171
- this.socket.binaryType = 'arraybuffer';
172
220
  this.terminal.loadAddon(fitAddon);
173
221
  this.terminal.open(this.container);
174
222
  fitAddon.fit();
175
- const shellAdapter = new ShellAdapter(this.terminal);
176
- this.observer = new ResizeObserver(() => {
177
- fitAddon.fit();
178
- });
223
+ this.shellAdapter = new ShellAdapter(this.terminal);
224
+ this.observer = new ResizeObserver(() => fitAddon.fit());
179
225
  this.observer.observe(this.container);
180
- this.terminal.writeln('\u001b[92mEstablishing Websocket connection... \u001b[39m \r\n');
181
226
  this.terminal.focus();
227
+ this.terminal.onData(data => {
228
+ if (this.firstDeviceMessageReceived()) {
229
+ const encodedToSend = this.shellAdapter.filterSendData(data);
230
+ if (encodedToSend.length) {
231
+ this.socket?.send(encodedToSend);
232
+ }
233
+ }
234
+ });
235
+ this.connect();
236
+ }
237
+ toggleFullscreen() {
238
+ if (document.fullscreenElement) {
239
+ document.exitFullscreen();
240
+ }
241
+ else {
242
+ this.container.requestFullscreen();
243
+ }
244
+ }
245
+ /** Opens (or re-opens) the remote-access WebSocket and wires up the frame handlers. */
246
+ connect() {
247
+ this.closeSocket();
248
+ this.firstDeviceMessageReceived.set(false);
249
+ this.awaitingHostKeyConfirmation = false;
250
+ this.socket = new WebSocket(this.remoteAccess.getWebSocketUri(this.deviceId, this.configurationId), 'binary');
251
+ this.socket.binaryType = 'arraybuffer';
252
+ this.terminal.writeln('Establishing Websocket connection...  \r\n');
182
253
  this.socket.onopen = () => {
183
- this.terminal.writeln('\u001b[92mWebsocket connection was established successfully, waiting for device... \u001b[39m \r\n');
254
+ this.terminal.writeln('Websocket connection was established successfully, waiting for device...  \r\n');
184
255
  };
185
256
  this.socket.onclose = () => {
257
+ if (this.awaitingHostKeyConfirmation) {
258
+ return;
259
+ }
186
260
  this.terminal.writeln('');
187
- this.terminal.writeln('\r\n\u001b[91mDevice disconnected. \u001b[39m \r\n');
188
- this.terminal.writeln('\r\n\u001b[91mWebsocket connection was closed. \u001b[39m \r\n');
261
+ this.terminal.writeln('\r\nDevice disconnected.  \r\n');
262
+ this.terminal.writeln('\r\nWebsocket connection was closed.  \r\n');
189
263
  };
190
264
  this.socket.onmessage = message => {
191
- if (!this.firstDeviceMessageReceived) {
192
- this.firstDeviceMessageReceived = true;
193
- this.terminal.writeln('\u001b[92mDevice connection was established successfully. \u001b[39m \r\n');
265
+ // On first use the backend prepends a single JSON control frame (host-key probe or error) and
266
+ // then closes the socket. Everything else is a raw telnet/binary shell stream handled as before.
267
+ if (!this.firstDeviceMessageReceived()) {
268
+ const frame = parseControlFrame(message.data);
269
+ if (frame) {
270
+ this.handleControlFrame(frame);
271
+ return;
272
+ }
273
+ this.firstDeviceMessageReceived.set(true);
274
+ this.terminal.writeln('Device connection was established successfully.  \r\n');
194
275
  }
195
- const filteredData = shellAdapter.filterReceiveData(message.data);
276
+ const filteredData = this.shellAdapter.filterReceiveData(message.data);
196
277
  if (filteredData.dataToSend.length) {
197
278
  this.socket.send(filteredData.dataToSend);
198
279
  }
@@ -200,30 +281,87 @@ class TerminalViewerComponent {
200
281
  this.terminal.write(filteredData.dataToDisplay);
201
282
  }
202
283
  };
203
- this.terminal.onData(data => {
204
- if (this.firstDeviceMessageReceived) {
205
- const encodedToSend = shellAdapter.filterSendData(data);
206
- if (encodedToSend.length) {
207
- this.socket.send(encodedToSend);
208
- }
209
- }
210
- });
211
284
  }
212
- toggleFullscreen() {
213
- if (document.fullscreenElement) {
214
- document.exitFullscreen();
285
+ handleControlFrame(frame) {
286
+ if (frame.type === 'hostkey') {
287
+ this.awaitingHostKeyConfirmation = true;
288
+ void this.confirmHostKey(frame);
215
289
  }
216
290
  else {
217
- this.container.requestFullscreen();
291
+ this.alertService.danger(this.translateService.instant(gettext('Host key verification failed.')), frame.message);
292
+ }
293
+ }
294
+ /** Shows the fingerprint confirmation dialog; on accept persists the key and reconnects. */
295
+ async confirmHostKey(frame) {
296
+ const changed = frame.matchesStored === false;
297
+ try {
298
+ await this.modalService.confirm(this.translateService.instant(changed ? gettext('Host key changed') : gettext('Confirm host key')), this.buildConfirmBody(frame, changed), changed ? Status.DANGER : Status.INFO, { ok: this.translateService.instant(gettext('Trust and connect')) }, {}, undefined,
299
+ // Require the user to retype a code before trusting a key that differs from the stored one.
300
+ changed);
301
+ }
302
+ catch {
303
+ // Dismissed: abort without persisting the key and without opening a session.
304
+ this.terminal.writeln(`\r\n${this.translateService.instant(gettext('Host key was not confirmed. Connection aborted.'))}  \r\n`);
305
+ return;
306
+ }
307
+ try {
308
+ await this.remoteAccess.updateHostKey(this.deviceId, this.configurationId, toStoredHostKey(frame));
309
+ this.connect();
310
+ }
311
+ catch {
312
+ this.alertService.danger(this.translateService.instant(gettext('Failed to save the host key.')));
313
+ }
314
+ }
315
+ buildConfirmBody(frame, changed) {
316
+ const endpoint = `<code>${this.escapeHtml(frame.hostname)}:${Number(frame.port)}</code>`;
317
+ const intro = changed
318
+ ? this.translateService.instant(gettext('The host key for {{ endpoint }} has changed since it was last stored. This can happen after a legitimate key rotation, but it can also indicate a man-in-the-middle attack. Only continue if you trust the new key.'), { endpoint })
319
+ : this.translateService.instant(gettext('Confirm the host key for {{ endpoint }}. Only continue if the fingerprint matches the server you expect.'), { endpoint });
320
+ const keyType = this.translateService.instant(gettext('Key type: {{ keyType }}'), {
321
+ keyType: this.escapeHtml(frame.keyType)
322
+ });
323
+ const fingerprint = this.translateService.instant(gettext('Fingerprint (SHA-256): {{ value }}'), {
324
+ value: `<code>${this.escapeHtml(frame.fingerprintSha256)}</code>`
325
+ });
326
+ return `${intro}<br><br>${keyType}<br>${fingerprint}`;
327
+ }
328
+ /** Escapes values interpolated into the modal's HTML body to prevent markup injection. */
329
+ escapeHtml(value) {
330
+ const entities = {
331
+ '&': '&amp;',
332
+ '<': '&lt;',
333
+ '>': '&gt;',
334
+ '"': '&quot;',
335
+ "'": '&#39;'
336
+ };
337
+ return String(value).replace(/[&<>"']/g, char => entities[char]);
338
+ }
339
+ closeSocket() {
340
+ if (!this.socket) {
341
+ return;
342
+ }
343
+ const socket = this.socket;
344
+ this.socket = null;
345
+ // Detach the close handler before closing so our own teardown never triggers the
346
+ // "device disconnected" banner — the async onclose would otherwise fire after this returns.
347
+ socket.onclose = null;
348
+ const stringToSend = 'exit\n';
349
+ const sendQueue = [];
350
+ for (let i = 0; i < stringToSend.length; i++) {
351
+ sendQueue.push(stringToSend.charCodeAt(i));
352
+ }
353
+ if (socket.readyState === WebSocket.OPEN) {
354
+ socket.send(new Uint8Array(sendQueue));
218
355
  }
356
+ socket.close();
219
357
  }
220
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: TerminalViewerComponent, deps: [{ token: i1.RemoteAccessService }, { token: i2.ActivatedRoute }], target: i0.ɵɵFactoryTarget.Component }); }
221
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: TerminalViewerComponent, isStandalone: true, selector: "c8y-terminal-viewer", ngImport: i0, template: "<c8y-title>Terminal Viewer: {{ title | translate }}</c8y-title>\n\n@if (firstDeviceMessageReceived) {\n <c8y-action-bar-item [placement]=\"'right'\">\n <button\n class=\"btn btn-link\"\n (click)=\"toggleFullscreen()\"\n >\n <i [c8yIcon]=\"'expand'\"></i>\n <span translate>Fullscreen</span>\n </button>\n </c8y-action-bar-item>\n}\n\n<div class=\"content-fullpage\">\n <div\n class=\"inner-scroll\"\n id=\"terminal-screen\"\n ></div>\n</div>\n", styles: [".xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;inset:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;inset:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}.xterm .xterm-accessibility-tree{-webkit-user-select:text;user-select:text;white-space:pre}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}\n/**\n * Copyright (c) 2014 The xterm.js authors. All rights reserved.\n * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)\n * https://github.com/chjj/term.js\n * @license MIT\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * Originally forked from (with the author's permission):\n * Fabrice Bellard's javascript vt100 for jslinux:\n * http://bellard.org/jslinux/\n * Copyright (c) 2011 Fabrice Bellard\n * The original design remains. The terminal itself\n * has been extended to include xterm CSI codes, among\n * other features.\n */\n"], dependencies: [{ kind: "component", type: TitleComponent, selector: "c8y-title", inputs: ["pageTitleUpdate"] }, { kind: "component", type: ActionBarItemComponent, selector: "c8y-action-bar-item", inputs: ["placement", "priority", "itemClass", "injector", "groupId", "inGroupPriority"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }], encapsulation: i0.ViewEncapsulation.None }); }
358
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: TerminalViewerComponent, deps: [{ token: i1.RemoteAccessService }, { token: i2.ActivatedRoute }, { token: i3.ModalService }, { token: i3.AlertService }, { token: i4.TranslateService }], target: i0.ɵɵFactoryTarget.Component }); }
359
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: TerminalViewerComponent, isStandalone: true, selector: "c8y-terminal-viewer", ngImport: i0, template: "<c8y-title>Terminal Viewer: {{ title | translate }}</c8y-title>\n\n@if (firstDeviceMessageReceived()) {\n <c8y-action-bar-item [placement]=\"'right'\">\n <button\n class=\"btn btn-link\"\n (click)=\"toggleFullscreen()\"\n >\n <i [c8yIcon]=\"'expand'\"></i>\n <span translate>Fullscreen</span>\n </button>\n </c8y-action-bar-item>\n}\n\n<div class=\"content-fullpage\">\n <div\n class=\"inner-scroll\"\n id=\"terminal-screen\"\n ></div>\n</div>\n", styles: [".xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;inset:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;inset:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}.xterm .xterm-accessibility-tree{-webkit-user-select:text;user-select:text;white-space:pre}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}\n/**\n * Copyright (c) 2014 The xterm.js authors. All rights reserved.\n * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)\n * https://github.com/chjj/term.js\n * @license MIT\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * Originally forked from (with the author's permission):\n * Fabrice Bellard's javascript vt100 for jslinux:\n * http://bellard.org/jslinux/\n * Copyright (c) 2011 Fabrice Bellard\n * The original design remains. The terminal itself\n * has been extended to include xterm CSI codes, among\n * other features.\n */\n"], dependencies: [{ kind: "component", type: TitleComponent, selector: "c8y-title", inputs: ["pageTitleUpdate"] }, { kind: "component", type: ActionBarItemComponent, selector: "c8y-action-bar-item", inputs: ["placement", "priority", "itemClass", "injector", "groupId", "inGroupPriority"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
222
360
  }
223
361
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: TerminalViewerComponent, decorators: [{
224
362
  type: Component,
225
- args: [{ selector: 'c8y-terminal-viewer', encapsulation: ViewEncapsulation.None, standalone: true, imports: [TitleComponent, C8yTranslatePipe, ActionBarItemComponent, IconDirective], template: "<c8y-title>Terminal Viewer: {{ title | translate }}</c8y-title>\n\n@if (firstDeviceMessageReceived) {\n <c8y-action-bar-item [placement]=\"'right'\">\n <button\n class=\"btn btn-link\"\n (click)=\"toggleFullscreen()\"\n >\n <i [c8yIcon]=\"'expand'\"></i>\n <span translate>Fullscreen</span>\n </button>\n </c8y-action-bar-item>\n}\n\n<div class=\"content-fullpage\">\n <div\n class=\"inner-scroll\"\n id=\"terminal-screen\"\n ></div>\n</div>\n", styles: [".xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;inset:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;inset:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}.xterm .xterm-accessibility-tree{-webkit-user-select:text;user-select:text;white-space:pre}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}\n/**\n * Copyright (c) 2014 The xterm.js authors. All rights reserved.\n * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)\n * https://github.com/chjj/term.js\n * @license MIT\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * Originally forked from (with the author's permission):\n * Fabrice Bellard's javascript vt100 for jslinux:\n * http://bellard.org/jslinux/\n * Copyright (c) 2011 Fabrice Bellard\n * The original design remains. The terminal itself\n * has been extended to include xterm CSI codes, among\n * other features.\n */\n"] }]
226
- }], ctorParameters: () => [{ type: i1.RemoteAccessService }, { type: i2.ActivatedRoute }] });
363
+ args: [{ selector: 'c8y-terminal-viewer', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, imports: [TitleComponent, C8yTranslatePipe, ActionBarItemComponent, IconDirective], template: "<c8y-title>Terminal Viewer: {{ title | translate }}</c8y-title>\n\n@if (firstDeviceMessageReceived()) {\n <c8y-action-bar-item [placement]=\"'right'\">\n <button\n class=\"btn btn-link\"\n (click)=\"toggleFullscreen()\"\n >\n <i [c8yIcon]=\"'expand'\"></i>\n <span translate>Fullscreen</span>\n </button>\n </c8y-action-bar-item>\n}\n\n<div class=\"content-fullpage\">\n <div\n class=\"inner-scroll\"\n id=\"terminal-screen\"\n ></div>\n</div>\n", styles: [".xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;inset:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;inset:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}.xterm .xterm-accessibility-tree{-webkit-user-select:text;user-select:text;white-space:pre}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}\n/**\n * Copyright (c) 2014 The xterm.js authors. All rights reserved.\n * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)\n * https://github.com/chjj/term.js\n * @license MIT\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * Originally forked from (with the author's permission):\n * Fabrice Bellard's javascript vt100 for jslinux:\n * http://bellard.org/jslinux/\n * Copyright (c) 2011 Fabrice Bellard\n * The original design remains. The terminal itself\n * has been extended to include xterm CSI codes, among\n * other features.\n */\n"] }]
364
+ }], ctorParameters: () => [{ type: i1.RemoteAccessService }, { type: i2.ActivatedRoute }, { type: i3.ModalService }, { type: i3.AlertService }, { type: i4.TranslateService }] });
227
365
 
228
366
  /**
229
367
  * Generated bundle index. Do not edit.
@@ -1 +1 @@
1
- {"version":3,"file":"c8y-ngx-components-remote-access-terminal-viewer.mjs","sources":["../../remote-access/terminal-viewer/telnet-negotiator.ts","../../remote-access/terminal-viewer/shell-adapter.ts","../../remote-access/terminal-viewer/terminal-viewer.component.ts","../../remote-access/terminal-viewer/terminal-viewer.component.html","../../remote-access/terminal-viewer/c8y-ngx-components-remote-access-terminal-viewer.ts"],"sourcesContent":["import type { Terminal } from '@xterm/xterm';\n\nexport class TelnetNegotiator {\n constructor(\n private terminal: Terminal,\n private termType: string\n ) {}\n\n setTermType(type) {\n this.termType = type;\n }\n\n pushStr(str, arr) {\n for (let i = 0; i < str.length; i++) {\n arr.push(str.charCodeAt(i));\n }\n }\n\n negotiate(data) {\n const arrUint8 = new Uint8Array(data);\n const arr = [...arrUint8.values()];\n\n const sendQueue = [];\n let stringToDisplay = '';\n let chr: number;\n let code: number;\n let value: number;\n\n while (arr.length > 0) {\n chr = arr.shift();\n switch (chr) {\n case 255: // IAC\n code = arr.shift();\n value = arr.shift();\n switch (code) {\n case 253: // DO\n if (value === 24) {\n // Terminal type\n sendQueue.push(255, 251, value);\n } else if (value === 31) {\n // NAWS (Negotiate About Window Size)\n // client answers WILL NAWS\n sendQueue.push(255, 251, 31);\n // client sends IAC SB NAWS 0 terminalSize.cols 0 terminalSize.rows IAC SE\n sendQueue.push(\n 255,\n 250,\n 31,\n 0,\n this.terminal.cols,\n 0,\n this.terminal.rows,\n 255,\n 240\n );\n } else {\n // Refuse other DO requests with a WONT\n sendQueue.push(255, 252, value);\n }\n break;\n case 251: // WILL\n if (value === 1) {\n // Affirm echo with DO\n sendQueue.push(255, 253, value);\n } else {\n // Reject other WILL offers with a DONT\n sendQueue.push(255, 254, value);\n }\n break;\n case 250: // SB (subnegotiation)\n if (value === 24) {\n // TERM-TYPE subnegotiation\n if (arr[0] === 1 && arr[1] === 255 && arr[2] === 240) {\n arr.shift();\n arr.shift();\n arr.shift();\n sendQueue.push(255, 250, 24, 0);\n this.pushStr(this.termType, sendQueue);\n sendQueue.push(255, 240);\n } else {\n console.warn('Invalid subnegotiation received' + arr);\n }\n } else {\n console.warn('Ignoring SB ' + value);\n }\n break;\n case 254: // DONT\n case 252: // WONT\n default:\n break;\n }\n break;\n case 242: // Data Mark (Synch)\n code = arr.shift();\n value = arr.shift();\n break;\n default: // everything else\n stringToDisplay += String.fromCharCode(chr);\n break;\n }\n }\n\n return {\n dataToSend: sendQueue.length ? new Uint8Array(sendQueue) : '',\n isOutput: !!stringToDisplay.length\n };\n }\n}\n","import { TelnetNegotiator } from './telnet-negotiator';\nimport type { Terminal } from '@xterm/xterm';\n\nexport class ShellAdapter {\n constructor(\n private terminal: Terminal,\n private termType = 'xterm-256color'\n ) {}\n\n filterNonPrintable(str: string) {\n // get rid of �\n const helpArr = str.split('�');\n str = helpArr.join('');\n\n return str;\n }\n\n setTermType(type) {\n this.termType = type;\n }\n\n filterReceiveData(data) {\n // negotiate telnet signals, if any\n const telnet = new TelnetNegotiator(this.terminal, this.termType);\n const telnetData = telnet.negotiate(data);\n\n // decode text for display\n const textDecoder = new TextDecoder();\n const decodedToDisplay = textDecoder.decode(data);\n\n return {\n dataToSend: telnetData.dataToSend,\n dataToDisplay: telnetData.isOutput ? this.filterNonPrintable(decodedToDisplay) : ''\n };\n }\n\n filterSendData(data) {\n const sendQueue = [];\n for (let i = 0; i < data.length; i++) {\n sendQueue.push(data.charCodeAt(i));\n }\n\n return sendQueue.length ? new Uint8Array(sendQueue) : '';\n }\n}\n","import { AfterViewInit, Component, OnDestroy, ViewEncapsulation } from '@angular/core';\nimport {\n ActionBarItemComponent,\n C8yTranslatePipe,\n IconDirective,\n TitleComponent\n} from '@c8y/ngx-components';\nimport { Terminal } from '@xterm/xterm';\nimport { FitAddon } from '@xterm/addon-fit';\nimport { ShellAdapter } from './shell-adapter';\nimport { RemoteAccessService } from '@c8y/ngx-components/remote-access/data';\nimport { ActivatedRoute } from '@angular/router';\n\n@Component({\n selector: 'c8y-terminal-viewer',\n templateUrl: './terminal-viewer.component.html',\n styleUrls: ['./terminal-viewer.component.scss'],\n encapsulation: ViewEncapsulation.None,\n standalone: true,\n imports: [TitleComponent, C8yTranslatePipe, ActionBarItemComponent, IconDirective]\n})\nexport class TerminalViewerComponent implements AfterViewInit, OnDestroy {\n title = '';\n container: HTMLElement | null = null;\n terminal: Terminal | null = null;\n socket: WebSocket | null = null;\n firstDeviceMessageReceived = false;\n readonly configurationId: string;\n readonly deviceId: string;\n protected observer: ResizeObserver;\n\n constructor(\n private remoteAccess: RemoteAccessService,\n private activatedRoute: ActivatedRoute\n ) {\n this.configurationId = this.activatedRoute.snapshot.params.configurationId;\n this.deviceId = this.activatedRoute.parent.snapshot.params.id;\n }\n\n ngOnDestroy(): void {\n this.observer?.disconnect();\n const stringToSend = 'exit\\n';\n const sendQueue = [];\n for (let i = 0; i < stringToSend.length; i++) {\n sendQueue.push(stringToSend.charCodeAt(i));\n }\n this.socket?.send(new Uint8Array(sendQueue));\n this.socket?.close();\n }\n\n ngAfterViewInit(): void {\n this.container = document.getElementById('terminal-screen');\n this.terminal = new Terminal({\n fontSize: 18,\n fontFamily: 'consolas, monospace',\n cursorBlink: true\n });\n const fitAddon = new FitAddon();\n this.socket = new WebSocket(\n this.remoteAccess.getWebSocketUri(this.deviceId, this.configurationId),\n 'binary'\n );\n this.socket.binaryType = 'arraybuffer';\n this.terminal.loadAddon(fitAddon);\n this.terminal.open(this.container);\n fitAddon.fit();\n const shellAdapter = new ShellAdapter(this.terminal);\n this.observer = new ResizeObserver(() => {\n fitAddon.fit();\n });\n this.observer.observe(this.container);\n\n this.terminal.writeln('\\u001b[92mEstablishing Websocket connection... \\u001b[39m \\r\\n');\n this.terminal.focus();\n this.socket.onopen = () => {\n this.terminal.writeln(\n '\\u001b[92mWebsocket connection was established successfully, waiting for device... \\u001b[39m \\r\\n'\n );\n };\n\n this.socket.onclose = () => {\n this.terminal.writeln('');\n this.terminal.writeln('\\r\\n\\u001b[91mDevice disconnected. \\u001b[39m \\r\\n');\n this.terminal.writeln('\\r\\n\\u001b[91mWebsocket connection was closed. \\u001b[39m \\r\\n');\n };\n\n this.socket.onmessage = message => {\n if (!this.firstDeviceMessageReceived) {\n this.firstDeviceMessageReceived = true;\n this.terminal.writeln(\n '\\u001b[92mDevice connection was established successfully. \\u001b[39m \\r\\n'\n );\n }\n const filteredData = shellAdapter.filterReceiveData(message.data);\n if (filteredData.dataToSend.length) {\n this.socket.send(filteredData.dataToSend);\n }\n if (filteredData.dataToDisplay.length) {\n this.terminal.write(filteredData.dataToDisplay);\n }\n };\n\n this.terminal.onData(data => {\n if (this.firstDeviceMessageReceived) {\n const encodedToSend = shellAdapter.filterSendData(data);\n\n if (encodedToSend.length) {\n this.socket.send(encodedToSend);\n }\n }\n });\n }\n\n toggleFullscreen() {\n if (document.fullscreenElement) {\n document.exitFullscreen();\n } else {\n this.container.requestFullscreen();\n }\n }\n}\n","<c8y-title>Terminal Viewer: {{ title | translate }}</c8y-title>\n\n@if (firstDeviceMessageReceived) {\n <c8y-action-bar-item [placement]=\"'right'\">\n <button\n class=\"btn btn-link\"\n (click)=\"toggleFullscreen()\"\n >\n <i [c8yIcon]=\"'expand'\"></i>\n <span translate>Fullscreen</span>\n </button>\n </c8y-action-bar-item>\n}\n\n<div class=\"content-fullpage\">\n <div\n class=\"inner-scroll\"\n id=\"terminal-screen\"\n ></div>\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"c8y-ngx-components-remote-access-terminal-viewer.mjs","sources":["../../remote-access/terminal-viewer/telnet-negotiator.ts","../../remote-access/terminal-viewer/shell-adapter.ts","../../remote-access/terminal-viewer/host-key.model.ts","../../remote-access/terminal-viewer/terminal-viewer.component.ts","../../remote-access/terminal-viewer/terminal-viewer.component.html","../../remote-access/terminal-viewer/c8y-ngx-components-remote-access-terminal-viewer.ts"],"sourcesContent":["import type { Terminal } from '@xterm/xterm';\n\nexport class TelnetNegotiator {\n constructor(\n private terminal: Terminal,\n private termType: string\n ) {}\n\n setTermType(type) {\n this.termType = type;\n }\n\n pushStr(str, arr) {\n for (let i = 0; i < str.length; i++) {\n arr.push(str.charCodeAt(i));\n }\n }\n\n negotiate(data) {\n const arrUint8 = new Uint8Array(data);\n const arr = [...arrUint8.values()];\n\n const sendQueue = [];\n let stringToDisplay = '';\n let chr: number;\n let code: number;\n let value: number;\n\n while (arr.length > 0) {\n chr = arr.shift();\n switch (chr) {\n case 255: // IAC\n code = arr.shift();\n value = arr.shift();\n switch (code) {\n case 253: // DO\n if (value === 24) {\n // Terminal type\n sendQueue.push(255, 251, value);\n } else if (value === 31) {\n // NAWS (Negotiate About Window Size)\n // client answers WILL NAWS\n sendQueue.push(255, 251, 31);\n // client sends IAC SB NAWS 0 terminalSize.cols 0 terminalSize.rows IAC SE\n sendQueue.push(\n 255,\n 250,\n 31,\n 0,\n this.terminal.cols,\n 0,\n this.terminal.rows,\n 255,\n 240\n );\n } else {\n // Refuse other DO requests with a WONT\n sendQueue.push(255, 252, value);\n }\n break;\n case 251: // WILL\n if (value === 1) {\n // Affirm echo with DO\n sendQueue.push(255, 253, value);\n } else {\n // Reject other WILL offers with a DONT\n sendQueue.push(255, 254, value);\n }\n break;\n case 250: // SB (subnegotiation)\n if (value === 24) {\n // TERM-TYPE subnegotiation\n if (arr[0] === 1 && arr[1] === 255 && arr[2] === 240) {\n arr.shift();\n arr.shift();\n arr.shift();\n sendQueue.push(255, 250, 24, 0);\n this.pushStr(this.termType, sendQueue);\n sendQueue.push(255, 240);\n } else {\n console.warn('Invalid subnegotiation received' + arr);\n }\n } else {\n console.warn('Ignoring SB ' + value);\n }\n break;\n case 254: // DONT\n case 252: // WONT\n default:\n break;\n }\n break;\n case 242: // Data Mark (Synch)\n code = arr.shift();\n value = arr.shift();\n break;\n default: // everything else\n stringToDisplay += String.fromCharCode(chr);\n break;\n }\n }\n\n return {\n dataToSend: sendQueue.length ? new Uint8Array(sendQueue) : '',\n isOutput: !!stringToDisplay.length\n };\n }\n}\n","import { TelnetNegotiator } from './telnet-negotiator';\nimport type { Terminal } from '@xterm/xterm';\n\nexport class ShellAdapter {\n constructor(\n private terminal: Terminal,\n private termType = 'xterm-256color'\n ) {}\n\n filterNonPrintable(str: string) {\n // get rid of �\n const helpArr = str.split('�');\n str = helpArr.join('');\n\n return str;\n }\n\n setTermType(type) {\n this.termType = type;\n }\n\n filterReceiveData(data) {\n // negotiate telnet signals, if any\n const telnet = new TelnetNegotiator(this.terminal, this.termType);\n const telnetData = telnet.negotiate(data);\n\n // decode text for display\n const textDecoder = new TextDecoder();\n const decodedToDisplay = textDecoder.decode(data);\n\n return {\n dataToSend: telnetData.dataToSend,\n dataToDisplay: telnetData.isOutput ? this.filterNonPrintable(decodedToDisplay) : ''\n };\n }\n\n filterSendData(data) {\n const sendQueue = [];\n for (let i = 0; i < data.length; i++) {\n sendQueue.push(data.charCodeAt(i));\n }\n\n return sendQueue.length ? new Uint8Array(sendQueue) : '';\n }\n}\n","/**\n * Wire model for the Cloud Remote Access first-use host-key confirmation flow (DM-6421).\n *\n * The remote-access WebSocket normally carries a raw telnet/binary shell stream. When the backend\n * runs a host-key probe (first use, `hostkey-autosave=false`, no key stored yet) it prepends a single\n * JSON control frame — delivered as a binary WebSocket frame — and then tears the socket down. The UI\n * distinguishes such a control frame from terminal bytes via the `type` discriminator.\n */\n\n/** Server host key captured during KEX, pushed to the UI for confirmation. */\nexport interface HostKeyProbeMessage {\n type: 'hostkey';\n hostname: string;\n port: number;\n /** SSH key type, e.g. `ssh-ed25519`, `ssh-rsa`. */\n keyType: string;\n /** Base64 of the raw SSH wire encoding of the key. */\n publicKey: string;\n /** OpenSSH-style SHA-256 fingerprint, e.g. `SHA256:abc123…`. Shown to the user for confirmation. */\n fingerprintSha256: string;\n /**\n * `null` = nothing stored yet (genuine first use); `true` = live key equals the stored key (safe);\n * `false` = live key differs from the stored key (rotation or MITM).\n */\n matchesStored: boolean | null;\n}\n\n/** Error raised while probing; surfaced to the user instead of a terminal session. */\nexport interface HostKeyErrorMessage {\n type: 'error';\n /** Machine-readable error identifier (contract shared with the backend `sendProbeError` frame). */\n code?: string;\n message: string;\n}\n\nexport type RemoteAccessControlFrame = HostKeyProbeMessage | HostKeyErrorMessage;\n\n/**\n * Attempts to parse an incoming WebSocket frame as a remote-access control frame. Returns the parsed\n * frame for a recognised `type`, or `null` when the data is a raw terminal byte stream (the normal\n * case) — the caller then falls through to the telnet/xterm path. Never throws.\n */\nexport function parseControlFrame(data: unknown): RemoteAccessControlFrame | null {\n if (data === null || typeof data !== 'object') {\n return null;\n }\n let bytes: Uint8Array;\n try {\n // The socket uses binaryType='arraybuffer', so frames arrive as ArrayBuffer. Constructing a\n // Uint8Array works across JS realms, unlike `instanceof ArrayBuffer` (which breaks under jsdom).\n bytes = data instanceof Uint8Array ? data : new Uint8Array(data as ArrayBuffer);\n } catch {\n return null;\n }\n // Control frames are JSON objects; a raw shell stream almost never starts with '{' (0x7b), so this\n // cheaply skips the JSON.parse attempt for terminal data.\n if (bytes.length === 0 || bytes[0] !== 0x7b) {\n return null;\n }\n try {\n const parsed = JSON.parse(new TextDecoder().decode(bytes));\n if (parsed?.type === 'hostkey' || parsed?.type === 'error') {\n return parsed as RemoteAccessControlFrame;\n }\n } catch {\n // Not JSON — raw terminal data that happened to start with '{'. Fall through to the terminal path.\n }\n return null;\n}\n\n/** Composes the OpenSSH authorized_keys-style string persisted in the configuration credentials. */\nexport function toStoredHostKey(frame: HostKeyProbeMessage): string {\n return `${frame.keyType} ${frame.publicKey}`;\n}\n","import {\n AfterViewInit,\n ChangeDetectionStrategy,\n Component,\n OnDestroy,\n signal,\n ViewEncapsulation\n} from '@angular/core';\nimport {\n ActionBarItemComponent,\n AlertService,\n C8yTranslatePipe,\n IconDirective,\n ModalService,\n Status,\n TitleComponent\n} from '@c8y/ngx-components';\nimport { gettext } from '@c8y/ngx-components/gettext';\nimport { TranslateService } from '@ngx-translate/core';\nimport { Terminal } from '@xterm/xterm';\nimport { FitAddon } from '@xterm/addon-fit';\nimport { ShellAdapter } from './shell-adapter';\nimport { RemoteAccessService } from '@c8y/ngx-components/remote-access/data';\nimport { ActivatedRoute } from '@angular/router';\nimport {\n HostKeyProbeMessage,\n parseControlFrame,\n RemoteAccessControlFrame,\n toStoredHostKey\n} from './host-key.model';\n\n@Component({\n selector: 'c8y-terminal-viewer',\n templateUrl: './terminal-viewer.component.html',\n styleUrls: ['./terminal-viewer.component.scss'],\n encapsulation: ViewEncapsulation.None,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [TitleComponent, C8yTranslatePipe, ActionBarItemComponent, IconDirective]\n})\nexport class TerminalViewerComponent implements AfterViewInit, OnDestroy {\n title = '';\n container: HTMLElement | null = null;\n terminal: Terminal | null = null;\n socket: WebSocket | null = null;\n /** True once real terminal bytes start flowing (not while a host-key control frame is handled). */\n readonly firstDeviceMessageReceived = signal(false);\n readonly configurationId: string;\n readonly deviceId: string;\n protected observer: ResizeObserver;\n\n private shellAdapter: ShellAdapter;\n /** Suppresses the \"device disconnected\" banner while a probe socket is torn down for confirmation. */\n private awaitingHostKeyConfirmation = false;\n\n constructor(\n private remoteAccess: RemoteAccessService,\n private activatedRoute: ActivatedRoute,\n private modalService: ModalService,\n private alertService: AlertService,\n private translateService: TranslateService\n ) {\n this.configurationId = this.activatedRoute.snapshot.params.configurationId;\n this.deviceId = this.activatedRoute.parent.snapshot.params.id;\n }\n\n ngOnDestroy(): void {\n this.observer?.disconnect();\n this.closeSocket();\n }\n\n ngAfterViewInit(): void {\n this.container = document.getElementById('terminal-screen');\n this.terminal = new Terminal({\n fontSize: 18,\n fontFamily: 'consolas, monospace',\n cursorBlink: true\n });\n const fitAddon = new FitAddon();\n this.terminal.loadAddon(fitAddon);\n this.terminal.open(this.container);\n fitAddon.fit();\n this.shellAdapter = new ShellAdapter(this.terminal);\n this.observer = new ResizeObserver(() => fitAddon.fit());\n this.observer.observe(this.container);\n this.terminal.focus();\n\n this.terminal.onData(data => {\n if (this.firstDeviceMessageReceived()) {\n const encodedToSend = this.shellAdapter.filterSendData(data);\n if (encodedToSend.length) {\n this.socket?.send(encodedToSend);\n }\n }\n });\n\n this.connect();\n }\n\n toggleFullscreen() {\n if (document.fullscreenElement) {\n document.exitFullscreen();\n } else {\n this.container.requestFullscreen();\n }\n }\n\n /** Opens (or re-opens) the remote-access WebSocket and wires up the frame handlers. */\n private connect(): void {\n this.closeSocket();\n this.firstDeviceMessageReceived.set(false);\n this.awaitingHostKeyConfirmation = false;\n\n this.socket = new WebSocket(\n this.remoteAccess.getWebSocketUri(this.deviceId, this.configurationId),\n 'binary'\n );\n this.socket.binaryType = 'arraybuffer';\n\n this.terminal.writeln('\u001b[92mEstablishing Websocket connection... \u001b[39m \\r\\n');\n this.socket.onopen = () => {\n this.terminal.writeln(\n '\u001b[92mWebsocket connection was established successfully, waiting for device... \u001b[39m \\r\\n'\n );\n };\n\n this.socket.onclose = () => {\n if (this.awaitingHostKeyConfirmation) {\n return;\n }\n this.terminal.writeln('');\n this.terminal.writeln('\\r\\n\u001b[91mDevice disconnected. \u001b[39m \\r\\n');\n this.terminal.writeln('\\r\\n\u001b[91mWebsocket connection was closed. \u001b[39m \\r\\n');\n };\n\n this.socket.onmessage = message => {\n // On first use the backend prepends a single JSON control frame (host-key probe or error) and\n // then closes the socket. Everything else is a raw telnet/binary shell stream handled as before.\n if (!this.firstDeviceMessageReceived()) {\n const frame = parseControlFrame(message.data);\n if (frame) {\n this.handleControlFrame(frame);\n return;\n }\n this.firstDeviceMessageReceived.set(true);\n this.terminal.writeln('\u001b[92mDevice connection was established successfully. \u001b[39m \\r\\n');\n }\n const filteredData = this.shellAdapter.filterReceiveData(message.data);\n if (filteredData.dataToSend.length) {\n this.socket.send(filteredData.dataToSend);\n }\n if (filteredData.dataToDisplay.length) {\n this.terminal.write(filteredData.dataToDisplay);\n }\n };\n }\n\n private handleControlFrame(frame: RemoteAccessControlFrame): void {\n if (frame.type === 'hostkey') {\n this.awaitingHostKeyConfirmation = true;\n void this.confirmHostKey(frame);\n } else {\n this.alertService.danger(\n this.translateService.instant(gettext('Host key verification failed.')),\n frame.message\n );\n }\n }\n\n /** Shows the fingerprint confirmation dialog; on accept persists the key and reconnects. */\n private async confirmHostKey(frame: HostKeyProbeMessage): Promise<void> {\n const changed = frame.matchesStored === false;\n try {\n await this.modalService.confirm(\n this.translateService.instant(\n changed ? gettext('Host key changed') : gettext('Confirm host key')\n ),\n this.buildConfirmBody(frame, changed),\n changed ? Status.DANGER : Status.INFO,\n { ok: this.translateService.instant(gettext('Trust and connect')) },\n {},\n undefined,\n // Require the user to retype a code before trusting a key that differs from the stored one.\n changed\n );\n } catch {\n // Dismissed: abort without persisting the key and without opening a session.\n this.terminal.writeln(\n `\\r\\n\u001b[91m${this.translateService.instant(\n gettext('Host key was not confirmed. Connection aborted.')\n )} \u001b[39m \\r\\n`\n );\n return;\n }\n\n try {\n await this.remoteAccess.updateHostKey(\n this.deviceId,\n this.configurationId,\n toStoredHostKey(frame)\n );\n this.connect();\n } catch {\n this.alertService.danger(\n this.translateService.instant(gettext('Failed to save the host key.'))\n );\n }\n }\n\n private buildConfirmBody(frame: HostKeyProbeMessage, changed: boolean): string {\n const endpoint = `<code>${this.escapeHtml(frame.hostname)}:${Number(frame.port)}</code>`;\n const intro = changed\n ? this.translateService.instant(\n gettext(\n 'The host key for {{ endpoint }} has changed since it was last stored. This can happen after a legitimate key rotation, but it can also indicate a man-in-the-middle attack. Only continue if you trust the new key.'\n ),\n { endpoint }\n )\n : this.translateService.instant(\n gettext(\n 'Confirm the host key for {{ endpoint }}. Only continue if the fingerprint matches the server you expect.'\n ),\n { endpoint }\n );\n const keyType = this.translateService.instant(gettext('Key type: {{ keyType }}'), {\n keyType: this.escapeHtml(frame.keyType)\n });\n const fingerprint = this.translateService.instant(\n gettext('Fingerprint (SHA-256): {{ value }}'),\n {\n value: `<code>${this.escapeHtml(frame.fingerprintSha256)}</code>`\n }\n );\n return `${intro}<br><br>${keyType}<br>${fingerprint}`;\n }\n\n /** Escapes values interpolated into the modal's HTML body to prevent markup injection. */\n private escapeHtml(value: string): string {\n const entities: Record<string, string> = {\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&#39;'\n };\n return String(value).replace(/[&<>\"']/g, char => entities[char]);\n }\n\n private closeSocket(): void {\n if (!this.socket) {\n return;\n }\n const socket = this.socket;\n this.socket = null;\n // Detach the close handler before closing so our own teardown never triggers the\n // \"device disconnected\" banner — the async onclose would otherwise fire after this returns.\n socket.onclose = null;\n const stringToSend = 'exit\\n';\n const sendQueue = [];\n for (let i = 0; i < stringToSend.length; i++) {\n sendQueue.push(stringToSend.charCodeAt(i));\n }\n if (socket.readyState === WebSocket.OPEN) {\n socket.send(new Uint8Array(sendQueue));\n }\n socket.close();\n }\n}\n","<c8y-title>Terminal Viewer: {{ title | translate }}</c8y-title>\n\n@if (firstDeviceMessageReceived()) {\n <c8y-action-bar-item [placement]=\"'right'\">\n <button\n class=\"btn btn-link\"\n (click)=\"toggleFullscreen()\"\n >\n <i [c8yIcon]=\"'expand'\"></i>\n <span translate>Fullscreen</span>\n </button>\n </c8y-action-bar-item>\n}\n\n<div class=\"content-fullpage\">\n <div\n class=\"inner-scroll\"\n id=\"terminal-screen\"\n ></div>\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}