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. 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()orsendKeys(), 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.
[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:
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.
<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.
Content-Security-Policy: frame-src https://terminal.bank.example; script-src 'self' https://terminal.bank.exampleWhat 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 whoseframe-ancestorsis 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
AllowCopyoff 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-configfirst, thenwnd-gateway check-embed-tokenwith a token from your server. frame_refusedmeans the page’s origin isn’t inallowed_originsexactly, or the browser can’t reach the gateway.token_signaturemeans the secret or key pair differs between your server and the gateway.token_expiredusually means the two clocks differ by more than 30 seconds.token_usedmeans a token was used twice.not_allowedmeans the gateway’s or the token’sallow, or its connections, don’t include what was asked.