mirror of
https://github.com/tale/headplane.git
synced 2026-08-27 15:37:04 +00:00
feat: support resizing and other xterm.js addons
This commit is contained in:
@@ -70,6 +70,17 @@ func FollowMaster(agent *tsnet.TSAgent) {
|
|||||||
|
|
||||||
sshutil.CloseWebSSH(agent, sshPayload)
|
sshutil.CloseWebSSH(agent, sshPayload)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
case "ssh_resize":
|
||||||
|
var sshPayload sshutil.SSHResizePayload
|
||||||
|
err = cbor.Unmarshal(msg.Payload, &sshPayload)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Unable to unmarshal SSH resize payload: %s", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
sshutil.ResizeWebSSH(agent, sshPayload)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,12 @@ type SSHClosePayload struct {
|
|||||||
SessionId string `cbor:"sessionId"`
|
SessionId string `cbor:"sessionId"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SSHResizePayload struct {
|
||||||
|
SessionId string `cbor:"sessionId"`
|
||||||
|
Width int `cbor:"width"`
|
||||||
|
Height int `cbor:"height"`
|
||||||
|
}
|
||||||
|
|
||||||
func connectToTailscaleSSH(agent *tsnet.TSAgent, params SSHConnectPayload) (*ssh.Client, error) {
|
func connectToTailscaleSSH(agent *tsnet.TSAgent, params SSHConnectPayload) (*ssh.Client, error) {
|
||||||
log := util.GetLogger()
|
log := util.GetLogger()
|
||||||
addr := strings.Join([]string{params.Hostname, ":", strconv.Itoa(params.Port)}, "")
|
addr := strings.Join([]string{params.Hostname, ":", strconv.Itoa(params.Port)}, "")
|
||||||
@@ -96,7 +102,7 @@ func StartWebSSH(agent *tsnet.TSAgent, params SSHConnectPayload) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Resize event is possible via the control channel later
|
// Resize event is possible via the control channel later
|
||||||
err = sess.RequestPty("xterm-256color", 80, 40, modes)
|
err = sess.RequestPty("xterm-256color", 24, 80, modes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to request PTY for (%s): %s", params.SessionId, err)
|
log.Error("Failed to request PTY for (%s): %s", params.SessionId, err)
|
||||||
return
|
return
|
||||||
@@ -164,3 +170,32 @@ func CloseWebSSH(agent *tsnet.TSAgent, params SSHClosePayload) {
|
|||||||
RemoveSession(ctx.ID)
|
RemoveSession(ctx.ID)
|
||||||
log.Info("SSH session for %s closed", params.SessionId)
|
log.Info("SSH session for %s closed", params.SessionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ResizeWebSSH(agent *tsnet.TSAgent, params SSHResizePayload) {
|
||||||
|
log := util.GetLogger()
|
||||||
|
|
||||||
|
if agent == nil {
|
||||||
|
log.Error("tsnet.TSAgent is not initialized correctly")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.SessionId == "" || params.Width <= 0 || params.Height <= 0 {
|
||||||
|
log.Error("Invalid SSH resize parameters: %v", params)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("Resizing SSH session for session ID: %s to %dx%d", params.SessionId, params.Width, params.Height)
|
||||||
|
ctx, ok := lookupSession(params.SessionId)
|
||||||
|
if !ok {
|
||||||
|
log.Info("No active SSH session found for session ID: %s", params.SessionId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := ctx.Session.WindowChange(params.Height, params.Width)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to resize SSH session for (%s): %s", params.SessionId, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Resized SSH session for %s to %dx%d", params.SessionId, params.Width, params.Height)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,18 @@
|
|||||||
|
import { ClipboardAddon } from '@xterm/addon-clipboard';
|
||||||
|
import { FitAddon } from '@xterm/addon-fit';
|
||||||
|
import { Unicode11Addon } from '@xterm/addon-unicode11';
|
||||||
|
import { WebLinksAddon } from '@xterm/addon-web-links';
|
||||||
|
import { WebglAddon } from '@xterm/addon-webgl';
|
||||||
import * as xterm from '@xterm/xterm';
|
import * as xterm from '@xterm/xterm';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import '@xterm/xterm/css/xterm.css';
|
import '@xterm/xterm/css/xterm.css';
|
||||||
import { decode } from 'cborg';
|
import { decode, encode } from 'cborg';
|
||||||
import type { SSHFrameData } from '~/server/agent/dispatcher';
|
import type {
|
||||||
|
SSHDataCommand,
|
||||||
|
SSHFrameData,
|
||||||
|
SSHResizeCommand,
|
||||||
|
} from '~/server/agent/dispatcher';
|
||||||
|
import cn from '~/utils/cn';
|
||||||
import { useLiveData } from '~/utils/live-data';
|
import { useLiveData } from '~/utils/live-data';
|
||||||
|
|
||||||
interface XTermProps {
|
interface XTermProps {
|
||||||
@@ -19,19 +29,40 @@ export default function XTerm({ ws, sessionId, queue }: XTermProps) {
|
|||||||
|
|
||||||
const container = useRef<HTMLDivElement>(null);
|
const container = useRef<HTMLDivElement>(null);
|
||||||
const term = useRef<xterm.Terminal>(null);
|
const term = useRef<xterm.Terminal>(null);
|
||||||
|
const [isResizing, setIsResizing] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
pause();
|
pause();
|
||||||
|
|
||||||
const terminal = new xterm.Terminal({
|
const terminal = new xterm.Terminal({
|
||||||
|
allowProposedApi: true,
|
||||||
|
cursorBlink: true,
|
||||||
convertEol: true,
|
convertEol: true,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
cols: 80,
|
||||||
|
rows: 24,
|
||||||
theme: {
|
theme: {
|
||||||
background: '#1e1e1e',
|
background: '#1e1e1e',
|
||||||
foreground: '#ffffff',
|
foreground: '#ffffff',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
terminal.loadAddon(new Unicode11Addon());
|
||||||
|
terminal.loadAddon(new ClipboardAddon());
|
||||||
|
terminal.loadAddon(new WebLinksAddon());
|
||||||
|
terminal.unicode.activeVersion = '11';
|
||||||
|
|
||||||
|
const gl = new WebglAddon();
|
||||||
|
terminal.loadAddon(gl);
|
||||||
|
|
||||||
|
const fit = new FitAddon();
|
||||||
|
terminal.loadAddon(fit);
|
||||||
|
|
||||||
|
gl.onContextLoss(() => {
|
||||||
|
console.warn('WebGL context lost, falling back to canvas rendering');
|
||||||
|
gl.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
terminal.open(container.current!);
|
terminal.open(container.current!);
|
||||||
terminal.focus();
|
terminal.focus();
|
||||||
term.current = terminal;
|
term.current = terminal;
|
||||||
@@ -63,7 +94,15 @@ export default function XTerm({ ws, sessionId, queue }: XTermProps) {
|
|||||||
|
|
||||||
terminal.onData((input) => {
|
terminal.onData((input) => {
|
||||||
if (ws.readyState === WebSocket.OPEN) {
|
if (ws.readyState === WebSocket.OPEN) {
|
||||||
ws.send(input);
|
ws.send(
|
||||||
|
encode({
|
||||||
|
op: 'ssh_data',
|
||||||
|
payload: {
|
||||||
|
sessionId,
|
||||||
|
data: new TextEncoder().encode(input),
|
||||||
|
},
|
||||||
|
} satisfies SSHDataCommand),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
console.warn('WebSocket is not open, cannot send data');
|
console.warn('WebSocket is not open, cannot send data');
|
||||||
}
|
}
|
||||||
@@ -80,12 +119,57 @@ export default function XTerm({ ws, sessionId, queue }: XTermProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
ws.addEventListener('message', onMessage);
|
ws.addEventListener('message', onMessage);
|
||||||
|
const ro = new ResizeObserver(() => {
|
||||||
|
const before = {
|
||||||
|
cols: terminal.cols,
|
||||||
|
rows: terminal.rows,
|
||||||
|
};
|
||||||
|
|
||||||
|
fit.fit();
|
||||||
|
if (before.cols !== terminal.cols || before.rows !== terminal.rows) {
|
||||||
|
console.log(
|
||||||
|
`Resized terminal to ${terminal.cols} cols and ${terminal.rows} rows`,
|
||||||
|
);
|
||||||
|
ws.send(
|
||||||
|
encode({
|
||||||
|
op: 'ssh_resize',
|
||||||
|
payload: {
|
||||||
|
sessionId,
|
||||||
|
width: terminal.cols,
|
||||||
|
height: terminal.rows,
|
||||||
|
},
|
||||||
|
} satisfies SSHResizeCommand),
|
||||||
|
);
|
||||||
|
|
||||||
|
setIsResizing(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsResizing(false);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ro.observe(container.current!);
|
||||||
return () => {
|
return () => {
|
||||||
ws.removeEventListener('message', onMessage);
|
ws.removeEventListener('message', onMessage);
|
||||||
term.current?.dispose();
|
term.current?.dispose();
|
||||||
|
ro.disconnect();
|
||||||
};
|
};
|
||||||
}, [ws, queue]);
|
}, [ws, queue]);
|
||||||
|
|
||||||
return <div ref={container} style={{ height: '100%', width: '100%' }} />;
|
return (
|
||||||
|
<div className="relative w-full h-full group">
|
||||||
|
<div ref={container} className="w-full h-full" />
|
||||||
|
|
||||||
|
{term.current && isResizing ? (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2',
|
||||||
|
'px-4 py-2 bg-headplane-800 text-white rounded-full shadow z-50',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{term.current.cols}x{term.current.rows}
|
||||||
|
</div>
|
||||||
|
) : undefined}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export interface Command {
|
|||||||
payload: unknown;
|
payload: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SSHConnectCommand extends Command {
|
export interface SSHConnectCommand extends Command {
|
||||||
op: 'ssh_conn';
|
op: 'ssh_conn';
|
||||||
payload: {
|
payload: {
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
@@ -18,14 +18,31 @@ interface SSHConnectCommand extends Command {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SSHCloseCommand extends Command {
|
export interface SSHCloseCommand extends Command {
|
||||||
op: 'ssh_term';
|
op: 'ssh_term';
|
||||||
payload: {
|
payload: {
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
type AgentCommand = SSHConnectCommand | SSHCloseCommand;
|
export interface SSHResizeCommand extends Command {
|
||||||
|
op: 'ssh_resize';
|
||||||
|
payload: {
|
||||||
|
sessionId: string;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SSHDataCommand extends Command {
|
||||||
|
op: 'ssh_data';
|
||||||
|
payload: {
|
||||||
|
sessionId: string;
|
||||||
|
data: Uint8Array;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
type AgentCommand = SSHConnectCommand | SSHCloseCommand | SSHResizeCommand;
|
||||||
|
|
||||||
export async function dispatchCommand(
|
export async function dispatchCommand(
|
||||||
dispatcher: Writable,
|
dispatcher: Writable,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export type ChannelType = 0 | 1 | 2;
|
|||||||
interface SSHFrame {
|
interface SSHFrame {
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
channel: ChannelType;
|
channel: ChannelType;
|
||||||
payload: Blob | ArrayBufferLike | string;
|
payload: Buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function encodeSSHFrame(frame: SSHFrame) {
|
export async function encodeSSHFrame(frame: SSHFrame) {
|
||||||
@@ -23,17 +23,9 @@ export async function encodeSSHFrame(frame: SSHFrame) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload = Buffer.isBuffer(frame.payload)
|
|
||||||
? frame.payload
|
|
||||||
: typeof frame.payload === 'string'
|
|
||||||
? Buffer.from(frame.payload, 'utf8')
|
|
||||||
: frame.payload instanceof Blob
|
|
||||||
? Buffer.from(await frame.payload.arrayBuffer())
|
|
||||||
: Buffer.from(frame.payload);
|
|
||||||
|
|
||||||
// Size can only hold 4 bytes
|
// Size can only hold 4 bytes
|
||||||
if (payload.length > 0xffffffff) {
|
if (frame.payload.length > 0xffffffff) {
|
||||||
log.error('agent', 'SSH payload too large: %d bytes', payload.length);
|
log.error('agent', 'SSH payload too large: %d bytes', frame.payload.length);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +34,7 @@ export async function encodeSSHFrame(frame: SSHFrame) {
|
|||||||
1 + // Version
|
1 + // Version
|
||||||
1 + // Channel Type
|
1 + // Channel Type
|
||||||
(1 + sid.length) + // Session ID length + SID
|
(1 + sid.length) + // Session ID length + SID
|
||||||
(4 + payload.length); // Payload length + Payload
|
(4 + frame.payload.length); // Payload length + Payload
|
||||||
|
|
||||||
const buf = Buffer.alloc(frameSize);
|
const buf = Buffer.alloc(frameSize);
|
||||||
buf.write(MAGIC, 0, 'utf8');
|
buf.write(MAGIC, 0, 'utf8');
|
||||||
@@ -53,8 +45,8 @@ export async function encodeSSHFrame(frame: SSHFrame) {
|
|||||||
const offset = 7 + sid.length;
|
const offset = 7 + sid.length;
|
||||||
sid.copy(buf, 7);
|
sid.copy(buf, 7);
|
||||||
|
|
||||||
buf.writeUInt32BE(payload.length, offset);
|
buf.writeUInt32BE(frame.payload.length, offset);
|
||||||
payload.copy(buf, offset + 4);
|
frame.payload.copy(buf, offset + 4);
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+49
-7
@@ -1,10 +1,17 @@
|
|||||||
import { ChildProcess } from 'node:child_process';
|
import { ChildProcess } from 'node:child_process';
|
||||||
import { randomUUID } from 'node:crypto';
|
import { randomUUID } from 'node:crypto';
|
||||||
import type { Readable, Writable } from 'node:stream';
|
import type { Readable, Writable } from 'node:stream';
|
||||||
|
import { decode } from 'cborg';
|
||||||
import { Context } from 'hono';
|
import { Context } from 'hono';
|
||||||
import { WSContext, WSEvents } from 'hono/ws';
|
import { WSContext, WSEvents } from 'hono/ws';
|
||||||
import log from '~/utils/log';
|
import log from '~/utils/log';
|
||||||
import { dispatchCommand, dispatchWeb } from './dispatcher';
|
import {
|
||||||
|
Command,
|
||||||
|
SSHDataCommand,
|
||||||
|
SSHResizeCommand,
|
||||||
|
dispatchCommand,
|
||||||
|
dispatchWeb,
|
||||||
|
} from './dispatcher';
|
||||||
import { decodeSSHFrame, encodeSSHFrame } from './encoder';
|
import { decodeSSHFrame, encodeSSHFrame } from './encoder';
|
||||||
|
|
||||||
interface SSHConnection {
|
interface SSHConnection {
|
||||||
@@ -134,13 +141,48 @@ export class SSHMultiplexer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const encodedFrame = await encodeSSHFrame({
|
const wsData = Buffer.isBuffer(event.data)
|
||||||
sessionId,
|
? event.data
|
||||||
channel: 0, // stdin
|
: typeof event.data === 'string'
|
||||||
payload: event.data,
|
? Buffer.from(event.data, 'utf8')
|
||||||
});
|
: event.data instanceof Blob
|
||||||
|
? Buffer.from(await event.data.arrayBuffer())
|
||||||
|
: Buffer.from(event.data);
|
||||||
|
|
||||||
this.sshInput.write(encodedFrame);
|
const obj = decode(wsData) as Command;
|
||||||
|
if (obj.op === 'ssh_data') {
|
||||||
|
const data = obj as SSHDataCommand;
|
||||||
|
if (data.payload.sessionId !== sessionId) {
|
||||||
|
log.warn(
|
||||||
|
'agent',
|
||||||
|
'Received data for mismatched SSH session %s',
|
||||||
|
data.payload.sessionId,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const encodedFrame = await encodeSSHFrame({
|
||||||
|
sessionId,
|
||||||
|
channel: 0, // stdin
|
||||||
|
payload: Buffer.from(data.payload.data),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.sshInput.write(encodedFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.op === 'ssh_resize') {
|
||||||
|
const resize = obj as SSHResizeCommand;
|
||||||
|
if (resize.payload.sessionId !== sessionId) {
|
||||||
|
log.warn(
|
||||||
|
'agent',
|
||||||
|
'Received resize for mismatched SSH session %s',
|
||||||
|
resize.payload.sessionId,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await dispatchCommand(this.control, resize);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onClose: async (_, ws) => {
|
onClose: async (_, ws) => {
|
||||||
|
|||||||
@@ -28,6 +28,11 @@
|
|||||||
"@uiw/codemirror-theme-github": "^4.23.12",
|
"@uiw/codemirror-theme-github": "^4.23.12",
|
||||||
"@uiw/codemirror-theme-xcode": "^4.23.12",
|
"@uiw/codemirror-theme-xcode": "^4.23.12",
|
||||||
"@uiw/react-codemirror": "^4.23.12",
|
"@uiw/react-codemirror": "^4.23.12",
|
||||||
|
"@xterm/addon-clipboard": "^0.1.0",
|
||||||
|
"@xterm/addon-fit": "^0.10.0",
|
||||||
|
"@xterm/addon-unicode11": "^0.8.0",
|
||||||
|
"@xterm/addon-web-links": "^0.11.0",
|
||||||
|
"@xterm/addon-webgl": "^0.18.0",
|
||||||
"@xterm/xterm": "^5.5.0",
|
"@xterm/xterm": "^5.5.0",
|
||||||
"arktype": "^2.1.20",
|
"arktype": "^2.1.20",
|
||||||
"cborg": "^4.2.11",
|
"cborg": "^4.2.11",
|
||||||
|
|||||||
Generated
+66
@@ -61,6 +61,21 @@ importers:
|
|||||||
'@uiw/react-codemirror':
|
'@uiw/react-codemirror':
|
||||||
specifier: ^4.23.12
|
specifier: ^4.23.12
|
||||||
version: 4.23.12(@babel/runtime@7.27.3)(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)(@lezer/common@1.2.3))(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.7)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.8)(codemirror@6.0.1(@lezer/common@1.2.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
version: 4.23.12(@babel/runtime@7.27.3)(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)(@lezer/common@1.2.3))(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.7)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.8)(codemirror@6.0.1(@lezer/common@1.2.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@xterm/addon-clipboard':
|
||||||
|
specifier: ^0.1.0
|
||||||
|
version: 0.1.0(@xterm/xterm@5.5.0)
|
||||||
|
'@xterm/addon-fit':
|
||||||
|
specifier: ^0.10.0
|
||||||
|
version: 0.10.0(@xterm/xterm@5.5.0)
|
||||||
|
'@xterm/addon-unicode11':
|
||||||
|
specifier: ^0.8.0
|
||||||
|
version: 0.8.0(@xterm/xterm@5.5.0)
|
||||||
|
'@xterm/addon-web-links':
|
||||||
|
specifier: ^0.11.0
|
||||||
|
version: 0.11.0(@xterm/xterm@5.5.0)
|
||||||
|
'@xterm/addon-webgl':
|
||||||
|
specifier: ^0.18.0
|
||||||
|
version: 0.18.0(@xterm/xterm@5.5.0)
|
||||||
'@xterm/xterm':
|
'@xterm/xterm':
|
||||||
specifier: ^5.5.0
|
specifier: ^5.5.0
|
||||||
version: 5.5.0
|
version: 5.5.0
|
||||||
@@ -1594,6 +1609,31 @@ packages:
|
|||||||
react: '>=16.8.0'
|
react: '>=16.8.0'
|
||||||
react-dom: '>=16.8.0'
|
react-dom: '>=16.8.0'
|
||||||
|
|
||||||
|
'@xterm/addon-clipboard@0.1.0':
|
||||||
|
resolution: {integrity: sha512-zdoM7p53T5sv/HbRTyp4hY0kKmEQ3MZvAvEtiXqNIHc/JdpqwByCtsTaQF5DX2n4hYdXRPO4P/eOS0QEhX1nPw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@xterm/xterm': ^5.4.0
|
||||||
|
|
||||||
|
'@xterm/addon-fit@0.10.0':
|
||||||
|
resolution: {integrity: sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@xterm/xterm': ^5.0.0
|
||||||
|
|
||||||
|
'@xterm/addon-unicode11@0.8.0':
|
||||||
|
resolution: {integrity: sha512-LxinXu8SC4OmVa6FhgwsVCBZbr8WoSGzBl2+vqe8WcQ6hb1r6Gj9P99qTNdPiFPh4Ceiu2pC8xukZ6+2nnh49Q==}
|
||||||
|
peerDependencies:
|
||||||
|
'@xterm/xterm': ^5.0.0
|
||||||
|
|
||||||
|
'@xterm/addon-web-links@0.11.0':
|
||||||
|
resolution: {integrity: sha512-nIHQ38pQI+a5kXnRaTgwqSHnX7KE6+4SVoceompgHL26unAxdfP6IPqUTSYPQgSwM56hsElfoNrrW5V7BUED/Q==}
|
||||||
|
peerDependencies:
|
||||||
|
'@xterm/xterm': ^5.0.0
|
||||||
|
|
||||||
|
'@xterm/addon-webgl@0.18.0':
|
||||||
|
resolution: {integrity: sha512-xCnfMBTI+/HKPdRnSOHaJDRqEpq2Ugy8LEj9GiY4J3zJObo3joylIFaMvzBwbYRg8zLtkO0KQaStCeSfoaI2/w==}
|
||||||
|
peerDependencies:
|
||||||
|
'@xterm/xterm': ^5.0.0
|
||||||
|
|
||||||
'@xterm/xterm@5.5.0':
|
'@xterm/xterm@5.5.0':
|
||||||
resolution: {integrity: sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==}
|
resolution: {integrity: sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==}
|
||||||
|
|
||||||
@@ -1989,6 +2029,9 @@ packages:
|
|||||||
jose@6.0.11:
|
jose@6.0.11:
|
||||||
resolution: {integrity: sha512-QxG7EaliDARm1O1S8BGakqncGT9s25bKL1WSf6/oa17Tkqwi8D2ZNglqCF+DsYF88/rV66Q/Q2mFAy697E1DUg==}
|
resolution: {integrity: sha512-QxG7EaliDARm1O1S8BGakqncGT9s25bKL1WSf6/oa17Tkqwi8D2ZNglqCF+DsYF88/rV66Q/Q2mFAy697E1DUg==}
|
||||||
|
|
||||||
|
js-base64@3.7.7:
|
||||||
|
resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==}
|
||||||
|
|
||||||
js-tokens@4.0.0:
|
js-tokens@4.0.0:
|
||||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||||
|
|
||||||
@@ -4719,6 +4762,27 @@ snapshots:
|
|||||||
- '@codemirror/lint'
|
- '@codemirror/lint'
|
||||||
- '@codemirror/search'
|
- '@codemirror/search'
|
||||||
|
|
||||||
|
'@xterm/addon-clipboard@0.1.0(@xterm/xterm@5.5.0)':
|
||||||
|
dependencies:
|
||||||
|
'@xterm/xterm': 5.5.0
|
||||||
|
js-base64: 3.7.7
|
||||||
|
|
||||||
|
'@xterm/addon-fit@0.10.0(@xterm/xterm@5.5.0)':
|
||||||
|
dependencies:
|
||||||
|
'@xterm/xterm': 5.5.0
|
||||||
|
|
||||||
|
'@xterm/addon-unicode11@0.8.0(@xterm/xterm@5.5.0)':
|
||||||
|
dependencies:
|
||||||
|
'@xterm/xterm': 5.5.0
|
||||||
|
|
||||||
|
'@xterm/addon-web-links@0.11.0(@xterm/xterm@5.5.0)':
|
||||||
|
dependencies:
|
||||||
|
'@xterm/xterm': 5.5.0
|
||||||
|
|
||||||
|
'@xterm/addon-webgl@0.18.0(@xterm/xterm@5.5.0)':
|
||||||
|
dependencies:
|
||||||
|
'@xterm/xterm': 5.5.0
|
||||||
|
|
||||||
'@xterm/xterm@5.5.0': {}
|
'@xterm/xterm@5.5.0': {}
|
||||||
|
|
||||||
acorn@8.14.1:
|
acorn@8.14.1:
|
||||||
@@ -5093,6 +5157,8 @@ snapshots:
|
|||||||
|
|
||||||
jose@6.0.11: {}
|
jose@6.0.11: {}
|
||||||
|
|
||||||
|
js-base64@3.7.7: {}
|
||||||
|
|
||||||
js-tokens@4.0.0: {}
|
js-tokens@4.0.0: {}
|
||||||
|
|
||||||
js-yaml@4.1.0:
|
js-yaml@4.1.0:
|
||||||
|
|||||||
Reference in New Issue
Block a user