# Embed Terminal Sessions in a Web App

Source: https://wndsoftware.com/docs/embed-kit

### Embed kit: a terminal inside your own web apps

Show a live mainframe or IBM i session inside a portal, a CRM or a help desk tool, and let the page read the screen and type into it, as far as IT allows.

Your page loads the embed kit from your own WND Terminal gateway and calls `WndTerminal.mount()`. Your server signs a short-lived token for the person signed in. The gateway shows the terminal only inside pages IT lists, and your page can read the screen, type and react when it changes.

The embed kit puts a live WND Terminal session inside a web application of your own, such as a customer portal, a CRM, a help desk tool or an intranet page. The person uses the mainframe or IBM i screen right there. Your page can read the screen, type into it and react when it changes, so a CRM can open the customer’s record on the mainframe when an agent picks a customer.

It runs on [WND Terminal in a browser](https://wndsoftware.com/browser). Everything the gateway does for its own page holds for an embedded terminal too, including your policy, masking, which hosts can be reached, limits, seats and the audit log.


### How it works

- Your page loads the kit from your gateway and calls `WndTerminal.mount()`. The kit adds a frame whose page comes from the gateway.
- Your server signs an embed token  for the person signed in to your application. It says who they are, which page may show the terminal, and for how long.
- The gateway checks the token and opens the session. The terminal works in the frame as it does in WND Terminal itself.
- Your page asks the frame for things, such as `getScreenText()` or `sendKeys()`, and the gateway decides what your page may do.

Your page never holds the session or talks to the host. It talks only to the frame, and only in the ways the gateway allows.


### 1. Turn it on in the gateway

Add an `[embed]` section to `gateway.toml`, with the address of each web application that may show terminals and a secret your server and the gateway share.


```text
[embed]
allowed_origins = ["https://crm.bank.example"]
secret_file = "/etc/wnd-gateway/embed-secret"     # openssl rand -base64 48
allow = ["keyboard", "read", "write"]
connections = ["Production (TSO)"]
```

Then check it with `wnd-gateway check-config` and restart the gateway.

On a small screen, swipe the table to read all columns.


| Setting | Default | What it does |
| --- | --- | --- |
| `allowed_origins` | Required | The web applications that may show embedded terminals, such as `"https://crm.bank.example"` |
| `secret` or `secret_file` |  | The shared secret for HS256 tokens, at least 32 characters |
| `public_key_file` |  | For RS256 tokens, the public key of your server’s key pair, so the gateway holds nothing that can make tokens |
| `issuer` | None | If set, every token’s `iss` must match it |
| `allow` | `["keyboard", "read", "write"]` | What embedded terminals may do |
| `connections` | Any | The policy’s connections an embedded terminal may open |
| `max_token_seconds` | 300 | The longest a token may be good for |


### 2. Make embed tokens on your server

Add an endpoint that answers only people signed in to your application and returns a token for that person. An embed token is a JSON Web Token signed with HS256 or RS256, so any language with HMAC-SHA-256 or a JWT library can make one. In Node.js:


```text
import { createHmac, randomUUID } from "node:crypto";

export function embedToken({ gateway, origin, user, secret, connection, seconds = 60 }) {
  const now = Math.floor(Date.now() / 1000);
  const claims = { sub: user, aud: gateway, origin, connection, iat: now, exp: now + seconds, jti: randomUUID() };
  const b64 = (o) => Buffer.from(JSON.stringify(o)).toString("base64url");
  const signed = `${b64({ alg: "HS256", typ: "JWT" })}.${b64(claims)}`;
  return `${signed}.${createHmac("sha256", secret).update(signed).digest("base64url")}`;
}
```

On a small screen, swipe the table to read all columns.


| Claim | Needed | What it is |
| --- | --- | --- |
| `sub` | Yes | The person’s sign-in name. Their seat, usage and audit log are under it. |
| `aud` | Yes | The gateway’s address. A token for another gateway is refused. |
| `origin` | Yes | The page that may show the terminal. The token is refused anywhere else. |
| `iat`, `exp` | Yes | When it was made and when it expires. A minute is plenty, since the token is used only to start. |
| `jti` | Yes | A unique ID. Each token works once. |
| `connection` | No | The only connection this terminal may open |
| `allow` | No | Narrows what this terminal may do. It can’t allow more than the gateway does. |
| `name`, `groups` | No | The person’s name for the logs, and their groups for session recording |

`wnd-gateway check-embed-token` says whether the gateway would take a token, and why not, without using it up.


### 3. Show the terminal in your page

Every gateway serves the kit at `/sdk/wnd-terminal-embed.iife.js`.


```text
<div id="terminal"></div>
<script src="https://terminal.bank.example/sdk/wnd-terminal-embed.iife.js"></script>
<script>
  const term = WndTerminal.mount(document.getElementById("terminal"), {
    gateway: "https://terminal.bank.example",
    session: "Production (TSO)",
    token: () => fetch("/api/wnd-embed-token").then((r) => r.text()),
  });
  term.on("screenChanged", async () => {
    if ((await term.getScreenText()).includes("READY")) console.log("At the TSO READY prompt");
  });
</script>
```

Give `token` as a function, so each connect gets a fresh token. The same terminal is also an HTML element, `<wnd-terminal gateway="…" session="…">`, with the same methods and events.


### 4. Let your page frame the gateway

If your application sends a Content Security Policy, allow frames from the gateway, and its script.


```text
Content-Security-Policy: frame-src https://terminal.bank.example; script-src 'self' https://terminal.bank.example
```


### What an embedded terminal may do

On a small screen, swipe the table to read all columns.


| Permission | What it allows |
| --- | --- |
| `keyboard` | The person types in the terminal and clicks on its screen. |
| `read` | Your page reads the screen with `getScreenText()`, `getFields()` and `waitForText()`. |
| `write` | Your page types and sends keys with `typeText()`, `sendKeys()` and `setCursor()`. |

With only `keyboard`, people use the terminal in your page and your page can’t read or type in it. With only `read`, it’s a view your page reads and nobody types in. Macros, file transfer, Fill from Excel, Ask WND, printers and connecting to other hosts aren’t part of an embedded terminal. A call that isn’t allowed is refused with the code `not_allowed` and recorded in the audit log.


### The JavaScript API

On a small screen, swipe the table to read all columns.


| Method | What it does |
| --- | --- |
| `connect()`, `disconnect()` | Signs in with a token and opens the session, or closes it |
| `sendKeys(keys)` | Text and keys written as SendKey codes, such as `"LOGON TSO@E"` |
| `typeText(text)` | Types text exactly as it is |
| `getScreenText()` | The screen’s text. Password fields are blank and card numbers are masked. |
| `getFields()` | The screen’s fields with row, column, length, protected, numeric and text |
| `setCursor(row, col)` | Moves the cursor |
| `waitForText(text, options)` | Waits until text appears, or is gone |
| `getStatus()` | Connected, rows, columns, cursor and whether the keyboard is locked |
| `on(event, handler)` | Events `ready`, `connected`, `screenChanged`, `disconnected` and `error` |
| `setTheme()`, `fit()`, `focus()`, `destroy()` | Light or dark, resize, take the keyboard, remove the terminal |


### Security

- Only the pages IT allows can show it.  The gateway serves the embed page only to an origin in `allowed_origins`, with a Content Security Policy whose `frame-ancestors` is exactly that origin.
- Messages are checked both ways.  The kit takes messages only from its own frame at the gateway’s origin, and the frame only from the page it’s in.
- Tokens are short-lived and work once.  A token goes to the frame in a message, never in an address, so it isn’t in any server’s log. It’s refused when its signature, audience, origin, expiry or ID isn’t right.
- No cookies.  Browsers that block third-party cookies make no difference, and another site can’t use a person’s gateway sign-in.
- Your page never sees passwords.  Password fields are never given out, card numbers are masked, and with `AllowCopy` off your page can’t read the screen’s text.


### The audit log

The gateway records each embedded terminal under the token’s person, with the page it was shown in. The events are `embed.start`, `embed.refused` with the reason, `embed.blocked` for calls that weren’t allowed and `embed.end`, next to the usual session events. Tokens and secrets are never logged.


### Troubleshooting

- Run `wnd-gateway check-config` first, then `wnd-gateway check-embed-token` with a token from your server.
- `frame_refused` means the page’s origin isn’t in `allowed_origins` exactly, or the browser can’t reach the gateway.
- `token_signature` means the secret or key pair differs between your server and the gateway. `token_expired` usually means the two clocks differ by more than 30 seconds. `token_used` means a token was used twice.
- `not_allowed` means the gateway’s or the token’s `allow`, or its connections, don’t include what was asked.


### Related

[WND Terminal in a browser The gateway the embed kit runs on. Read →](https://wndsoftware.com/browser)
[Automation API The SendKey codes sendKeys() uses. Read →](https://wndsoftware.com/docs/automation-api)
[Security overview How the gateway handles data. Read →](https://wndsoftware.com/security)

### Questions


#### Can our page read passwords from the screen?

No. Password fields are never given out, card numbers are masked as your policy says, and with AllowCopy off the page can’t read the screen’s text at all.


#### Which pages can show the terminal?

Only the origins IT lists in the gateway’s allowed_origins. Browsers refuse to show the terminal inside any other page.


#### How long does an embed token last?

As long as your server says, up to max_token_seconds (300 by default). Each token works once, only to start the terminal, so a minute is plenty.


#### Where does the kit’s script come from?

From your own gateway, at /sdk/wnd-terminal-embed.iife.js. Nothing is loaded from WND Software.

Technical reviewer: [Albert Joseph](https://wndsoftware.com/authors/albert-joseph), WND Software.
