Class: Terminalwire::V2::Server::Context

Inherits:
Object
  • Object
show all
Defined in:
lib/terminalwire/v2/server/context.rb

Overview

The server's handle on the client's machine. CLI code calls these methods (puts, gets, file.read, ...) and the Context turns them into protocol frames via the Runtime. Output is one-way; input and filesystem ops are synchronous request/response.

Defined Under Namespace

Classes: Browser, Directory, File, RawInput, Stdin

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(runtime) ⇒ Context

Returns a new instance of Context.



12
13
14
15
16
17
# File 'lib/terminalwire/v2/server/context.rb', line 12

def initialize(runtime)
  @runtime = runtime
  @stdout_sid = nil
  @stderr_sid = nil
  @request = {}
end

Instance Attribute Details

#requestObject

The incoming HTTP connection profile, captured at the WebSocket upgrade (the Rack adapter sets it): { host:, ip:, user_agent:, headers: }. The client identifies itself in headers — chiefly a real User-Agent — so server code can see who connected (and a terminalwire about can light it up).



23
24
25
# File 'lib/terminalwire/v2/server/context.rb', line 23

def request
  @request
end

Instance Method Details

#browserObject



148
149
150
# File 'lib/terminalwire/v2/server/context.rb', line 148

def browser
  @browser ||= Browser.new(@runtime)
end

#capabilitiesObject

The capability set negotiated for this session — the intersection the client and server agreed on in the handshake. Branch on this to offer optional features only when the connected client supports them.



56
# File 'lib/terminalwire/v2/server/context.rb', line 56

def capabilities = @runtime.connection.capabilities

#client_versionObject

The client build version, parsed from the User-Agent ("terminalwire-exec/ (…)"). The version is a client-reported fact in a header, never derived from the URL. nil if the client sent no UA.



34
35
36
# File 'lib/terminalwire/v2/server/context.rb', line 34

def client_version
  user_agent && user_agent[%r{terminalwire-exec/(\S+)}, 1]
end

#directoryObject



144
145
146
# File 'lib/terminalwire/v2/server/context.rb', line 144

def directory
  @directory ||= Directory.new(@runtime)
end

#entitlementObject

The entitlement the client granted this session: its authority (origin), the path globs it will allow writes within, and the permitted schemes/env vars. The CLIENT enforces this; here it's a read-only view so server code can stay inside the sandbox (e.g. learn where it may persist a file).



51
# File 'lib/terminalwire/v2/server/context.rb', line 51

def entitlement = @runtime.entitlement

#env(name) ⇒ Object



104
105
106
# File 'lib/terminalwire/v2/server/context.rb', line 104

def env(name)
  @runtime.request(:env, :read, { "name" => name.to_s })
end

#exit(status = 0) ⇒ Object



152
153
154
# File 'lib/terminalwire/v2/server/context.rb', line 152

def exit(status = 0)
  @runtime.emit(Frames.exit(status: status))
end

#fileObject



140
141
142
# File 'lib/terminalwire/v2/server/context.rb', line 140

def file
  @file ||= File.new(@runtime)
end

#getpassObject



102
# File 'lib/terminalwire/v2/server/context.rb', line 102

def getpass = stdin.getpass

#getsObject

Convenience delegators (Thor's shell uses these).



101
# File 'lib/terminalwire/v2/server/context.rb', line 101

def gets = stdin.gets

#http_headersObject



29
# File 'lib/terminalwire/v2/server/context.rb', line 29

def http_headers = @request[:headers] || {}

#on_resize(&block) ⇒ Object

Register a callback fired when the client's window resizes.



78
# File 'lib/terminalwire/v2/server/context.rb', line 78

def on_resize(&block) = @runtime.on_resize(&block)

Output is flow-controlled and chunked by the runtime: write_data sizes each data frame to the client's available credit and blocks when the window is exhausted, so a fast server can't outrun a slow client.



83
84
85
86
# File 'lib/terminalwire/v2/server/context.rb', line 83

def print(data, stream: :stdout)
  sid = stream == :stderr ? (@stderr_sid ||= open(:stderr)) : (@stdout_sid ||= open(:stdout))
  @runtime.write_data(sid, data.to_s)
end

#program_argumentsObject



45
# File 'lib/terminalwire/v2/server/context.rb', line 45

def program_arguments = Array(@runtime.program && @runtime.program["args"])

#program_nameObject

The program name + arguments the client launched with (from the hello). CLI parsers (OptionParser, Thor, GLI, …) consume program_arguments.



44
# File 'lib/terminalwire/v2/server/context.rb', line 44

def program_name = @runtime.program && @runtime.program["name"]

#puts(data = "", stream: :stdout) ⇒ Object



88
89
90
# File 'lib/terminalwire/v2/server/context.rb', line 88

def puts(data = "", stream: :stdout)
  print("#{data}\n", stream: stream)
end

#query_terminal(sequence, timeout: 1.0) ⇒ Object

Query the client's terminal with a control sequence and return its reply (e.g. cursor-position report "\e[6n" -> "\e[row;colR"). The client writes the query to its tty and reads the terminal's response. Used by advanced TUI libraries that probe the terminal. Support is advertised via the terminal-query capability (see #capabilities); a client without a tty answers with an io error. (Enforcement isn't wired yet — this issues the request regardless — so check #capabilities yourself if that matters.)



136
137
138
# File 'lib/terminalwire/v2/server/context.rb', line 136

def query_terminal(sequence, timeout: 1.0)
  @runtime.request(:terminal, :query, { "sequence" => sequence.b, "timeout" => timeout })
end

#raw_input(mode: Protocol::Mode::RAW) ⇒ Object

Read keystrokes for the duration of the block: the client puts its terminal in mode and streams keypresses, restoring it when the block exits (even on error). Foundation for REPLs and interactive TUIs.

mode: :raw    — char-at-a-time, no echo, signals as bytes (TUIs)
mode: :cbreak — char-at-a-time, echo + signal keys on (single-key y/n)

context.raw_input { |keys| keys.each { |bytes| handle(bytes) } }


116
117
118
119
120
121
# File 'lib/terminalwire/v2/server/context.rb', line 116

def raw_input(mode: Protocol::Mode::RAW)
  sid = @runtime.open_raw_input(mode: mode.to_s)
  yield RawInput.new(@runtime, sid)
ensure
  @runtime.close_raw_input(sid) if sid
end

#read_keyObject

Single-keypress read with echo + signal keys left on (cbreak): the y/n prompt case. Returns the first byte(s) the user types.



125
126
127
# File 'lib/terminalwire/v2/server/context.rb', line 125

def read_key
  raw_input(mode: Protocol::Mode::CBREAK) { |keys| return keys.read }
end

#remote_ipObject

The client's real IP (through Fly/proxies), its User-Agent, and the raw incoming HTTP headers. nil/empty when unknown (e.g. non-HTTP transports).



27
# File 'lib/terminalwire/v2/server/context.rb', line 27

def remote_ip   = @request[:ip]

#root_pathObject

The server-manageable root on the client — the USER subtree (~/.terminalwire/usr), so binary_path = root_path/bin = ~/.terminalwire/usr/bin (installed app launchers). It deliberately does NOT point at ~/.terminalwire: the system bin (the terminalwire-exec engine) and the control dir (authorities) live above this and are off-limits — a server can't reach them even with a grant. A SYMBOLIC tilde path the client expands + enforces; only the privileged terminalwire.com origin is granted ~/.terminalwire/usr/bin/** (installer-seeded).



75
# File 'lib/terminalwire/v2/server/context.rb', line 75

def root_path = Pathname.new("~/.terminalwire/usr")

#stdinObject



96
97
98
# File 'lib/terminalwire/v2/server/context.rb', line 96

def stdin
  @stdin ||= Stdin.new(@runtime)
end

#storage_pathObject

The client directory this origin may persist files into — its sandbox — derived from the first granted path glob (".../**" -> "..."). Returns nil if no writable path was granted. Used like:

context.file.write("#{context.storage_path}/session.json", data)


63
64
65
66
# File 'lib/terminalwire/v2/server/context.rb', line 63

def storage_path
  glob = entitlement && entitlement.dig("paths", 0, "glob")
  glob && glob.sub(%r{/\*\*\z}, "")
end

#terminalObject

The client's live terminal (rows/cols/tty?/color?), kept current by the runtime's read pump as resize frames arrive.



40
# File 'lib/terminalwire/v2/server/context.rb', line 40

def terminal = @runtime.terminal

#user_agentObject



28
# File 'lib/terminalwire/v2/server/context.rb', line 28

def user_agent  = @request[:user_agent]

#warn(data = "") ⇒ Object



92
93
94
# File 'lib/terminalwire/v2/server/context.rb', line 92

def warn(data = "")
  puts(data, stream: :stderr)
end