green-screen-proxy 1.1.2 → 1.2.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.
- package/README.md +93 -8
- package/dist/cli.js +0 -6
- package/dist/cli.js.map +1 -1
- package/dist/controller.d.ts +14 -0
- package/dist/controller.d.ts.map +1 -1
- package/dist/controller.js +27 -2
- package/dist/controller.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -16
- package/dist/index.js.map +1 -1
- package/dist/protocols/tn5250-handler.d.ts +3 -2
- package/dist/protocols/tn5250-handler.d.ts.map +1 -1
- package/dist/protocols/tn5250-handler.js +83 -25
- package/dist/protocols/tn5250-handler.js.map +1 -1
- package/dist/protocols/types.d.ts +28 -2
- package/dist/protocols/types.d.ts.map +1 -1
- package/dist/protocols/types.js +28 -0
- package/dist/protocols/types.js.map +1 -1
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +160 -34
- package/dist/routes.js.map +1 -1
- package/dist/server.js +1 -5
- package/dist/server.js.map +1 -1
- package/dist/session-store.d.ts +68 -0
- package/dist/session-store.d.ts.map +1 -0
- package/dist/session-store.js +40 -0
- package/dist/session-store.js.map +1 -0
- package/dist/session.d.ts +26 -3
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +83 -11
- package/dist/session.js.map +1 -1
- package/dist/tn5250/connection.d.ts +5 -0
- package/dist/tn5250/connection.d.ts.map +1 -1
- package/dist/tn5250/connection.js +25 -1
- package/dist/tn5250/connection.js.map +1 -1
- package/dist/tn5250/constants.d.ts +39 -3
- package/dist/tn5250/constants.d.ts.map +1 -1
- package/dist/tn5250/constants.js +51 -3
- package/dist/tn5250/constants.js.map +1 -1
- package/dist/tn5250/ebcdic-jp-builtin.d.ts +45 -0
- package/dist/tn5250/ebcdic-jp-builtin.d.ts.map +1 -0
- package/dist/tn5250/ebcdic-jp-builtin.js +124 -0
- package/dist/tn5250/ebcdic-jp-builtin.js.map +1 -0
- package/dist/tn5250/ebcdic-jp.d.ts +61 -0
- package/dist/tn5250/ebcdic-jp.d.ts.map +1 -0
- package/dist/tn5250/ebcdic-jp.js +188 -0
- package/dist/tn5250/ebcdic-jp.js.map +1 -0
- package/dist/tn5250/ebcdic.d.ts +13 -4
- package/dist/tn5250/ebcdic.d.ts.map +1 -1
- package/dist/tn5250/ebcdic.js +30 -8
- package/dist/tn5250/ebcdic.js.map +1 -1
- package/dist/tn5250/encoder.d.ts +41 -9
- package/dist/tn5250/encoder.d.ts.map +1 -1
- package/dist/tn5250/encoder.js +228 -41
- package/dist/tn5250/encoder.js.map +1 -1
- package/dist/tn5250/parser.d.ts +14 -0
- package/dist/tn5250/parser.d.ts.map +1 -1
- package/dist/tn5250/parser.js +428 -53
- package/dist/tn5250/parser.js.map +1 -1
- package/dist/tn5250/screen.d.ts +144 -24
- package/dist/tn5250/screen.d.ts.map +1 -1
- package/dist/tn5250/screen.js +244 -13
- package/dist/tn5250/screen.js.map +1 -1
- package/dist/websocket.d.ts +3 -0
- package/dist/websocket.d.ts.map +1 -1
- package/dist/websocket.js +106 -1
- package/dist/websocket.js.map +1 -1
- package/package.json +1 -1
- package/dist/mock/mock-routes.d.ts +0 -3
- package/dist/mock/mock-routes.d.ts.map +0 -1
- package/dist/mock/mock-routes.js +0 -232
- package/dist/mock/mock-routes.js.map +0 -1
package/README.md
CHANGED
|
@@ -12,18 +12,13 @@ npm install green-screen-proxy
|
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
npx green-screen-proxy # Start on port 3001
|
|
15
|
-
npx green-screen-proxy --mock # Mock mode (no real host needed)
|
|
16
15
|
npx green-screen-proxy --port 8080 # Custom port
|
|
17
16
|
npx green-screen-terminal # Proxy + web terminal UI (separate package)
|
|
18
17
|
```
|
|
19
18
|
|
|
20
|
-
###
|
|
19
|
+
### Connecting to a host
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
### Connecting to a real host
|
|
25
|
-
|
|
26
|
-
Without `--mock`, the proxy opens real TCP connections. The sign-in form in the React component collects host, port, protocol, and credentials — the proxy handles the rest.
|
|
21
|
+
The proxy opens real TCP connections. The sign-in form in the React component collects host, port, protocol, and credentials — the proxy handles the rest.
|
|
27
22
|
|
|
28
23
|
## Cloudflare Worker Deployment
|
|
29
24
|
|
|
@@ -68,7 +63,6 @@ await proxy.close();
|
|
|
68
63
|
| Option | Type | Default | Description |
|
|
69
64
|
|--------|------|---------|-------------|
|
|
70
65
|
| `port` | `number` | `3001` | Port to listen on |
|
|
71
|
-
| `mock` | `boolean` | `false` | Use mock screens |
|
|
72
66
|
|
|
73
67
|
### Returns
|
|
74
68
|
|
|
@@ -79,6 +73,97 @@ await proxy.close();
|
|
|
79
73
|
| `port` | `number` | The actual port (may differ if original was in use) |
|
|
80
74
|
| `close()` | `Promise<void>` | Stop the server |
|
|
81
75
|
|
|
76
|
+
## HTTP endpoints
|
|
77
|
+
|
|
78
|
+
All routes accept an `X-Session-Id` header (or `?sessionId=` query) to target a specific session; omit it when there's exactly one session and the proxy will use it by default.
|
|
79
|
+
|
|
80
|
+
| Method | Path | Purpose |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| `POST` | `/connect` | Open a session to a host. Optionally includes `username`/`password` for auto sign-in. |
|
|
83
|
+
| `POST` | `/disconnect` | Close the current session. |
|
|
84
|
+
| `POST` | `/reconnect` | Reconnect the current session's TCP socket. |
|
|
85
|
+
| `GET` | `/screen` | Read the latest `ScreenData` snapshot. |
|
|
86
|
+
| `GET` | `/status` | Read `ConnectionStatus`. |
|
|
87
|
+
| `POST` | `/send-text` | Type text at the current cursor. |
|
|
88
|
+
| `POST` | `/send-key` | Send a key (`Enter`, `F1`–`F24`, `Tab`, arrows, etc.). |
|
|
89
|
+
| `POST` | `/set-cursor` | Move cursor to `{row, col}`. |
|
|
90
|
+
| `POST` | `/batch` | Atomic batch of `{type: 'key'|'text'|'setCursor', ...}` operations. |
|
|
91
|
+
| `GET` | `/read-mdt` | **v1.2.0** — return input fields whose MDT bit is set. `?includeUnmodified=1` returns all input fields. |
|
|
92
|
+
| `POST` | `/session/resume` | **v1.2.0** — probe whether a session still exists; returns current status + screen. Use on page reload for REST-only clients. |
|
|
93
|
+
| `POST` | `/session/authenticated` | **v1.2.0** — flip the session status to `authenticated`. For integrators running their own sign-on cascade. |
|
|
94
|
+
| `POST` | `/wait-for-fields` | **v1.2.0** — wait until the current screen has at least `minFields` input fields (or `timeoutMs`). Short-circuits if already satisfied. |
|
|
95
|
+
|
|
96
|
+
## WebSocket protocol
|
|
97
|
+
|
|
98
|
+
Single endpoint at `ws(s)://host/ws`. Clients send JSON commands, receive JSON events.
|
|
99
|
+
|
|
100
|
+
Commands the client sends:
|
|
101
|
+
|
|
102
|
+
| `type` | Purpose |
|
|
103
|
+
|---|---|
|
|
104
|
+
| `connect` | Open a session (same body as `POST /connect`). |
|
|
105
|
+
| `reattach` | Re-bind to an existing session by `sessionId`. |
|
|
106
|
+
| `text` | Send text input. |
|
|
107
|
+
| `key` | Send a key. |
|
|
108
|
+
| `setCursor` | Move cursor. |
|
|
109
|
+
| `readMdt` | **v1.2.0** — request modified field values; response is `{type: 'mdt', data: {fields, modifiedOnly}}`. |
|
|
110
|
+
| `markAuthenticated` | **v1.2.0** — flip status to `authenticated`. |
|
|
111
|
+
| `waitForFields` | **v1.2.0** — wait for a screen with at least N input fields. |
|
|
112
|
+
| `disconnect` | Close the session. |
|
|
113
|
+
|
|
114
|
+
Events the proxy pushes:
|
|
115
|
+
|
|
116
|
+
| `type` | Meaning |
|
|
117
|
+
|---|---|
|
|
118
|
+
| `screen` | New `ScreenData` snapshot. |
|
|
119
|
+
| `status` | `ConnectionStatus` change. |
|
|
120
|
+
| `connected` | Session established after `connect`/`reattach`. |
|
|
121
|
+
| `cursor` | Lightweight cursor-only update (local ops like Tab/arrows). |
|
|
122
|
+
| `mdt` | Response to a `readMdt` command. |
|
|
123
|
+
| `session.lost` | **v1.2.0** — session died (TCP drop, idle timeout, destroy). |
|
|
124
|
+
| `session.resumed` | **v1.2.0** — a client successfully reattached to this session. |
|
|
125
|
+
| `error` | Generic error with a `message`. |
|
|
126
|
+
|
|
127
|
+
## Pluggable session store
|
|
128
|
+
|
|
129
|
+
Sessions live in an in-memory Map by default. Integrators can plug their own store (e.g. for multi-process routing via Redis) before the server accepts connections:
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import { createProxy, setSessionStore, type SessionStore } from 'green-screen-proxy';
|
|
133
|
+
|
|
134
|
+
class MyStore implements SessionStore {
|
|
135
|
+
set(id, session) { /* ... */ }
|
|
136
|
+
get(id) { /* ... */ }
|
|
137
|
+
delete(id) { /* ... */ }
|
|
138
|
+
has(id) { /* ... */ }
|
|
139
|
+
values() { /* ... */ }
|
|
140
|
+
size() { /* ... */ }
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
setSessionStore(new MyStore());
|
|
144
|
+
const proxy = await createProxy({ port: 3001 });
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The store holds live `Session` instances (each owns a TCP socket + parser state), so a cross-process store needs to additionally implement request routing to the owning process — that's out of scope for the interface itself.
|
|
148
|
+
|
|
149
|
+
### Session lifecycle events
|
|
150
|
+
|
|
151
|
+
Subscribe to the global lifecycle bus to observe session transitions at the server:
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
import { sessionLifecycle } from 'green-screen-proxy';
|
|
155
|
+
|
|
156
|
+
sessionLifecycle.on('session.lost', (sessionId, status) => {
|
|
157
|
+
console.log('session died:', sessionId, status.status);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
sessionLifecycle.on('session.resumed', (sessionId) => {
|
|
161
|
+
console.log('client reattached:', sessionId);
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
These same events are forwarded to WebSocket clients watching the affected session — clients subscribe on the adapter side via `WebSocketAdapter.onSessionLost()` / `onSessionResumed()`.
|
|
166
|
+
|
|
82
167
|
## How It Works
|
|
83
168
|
|
|
84
169
|
```
|
package/dist/cli.js
CHANGED
|
@@ -9,7 +9,6 @@ if (subcommand === 'deploy') {
|
|
|
9
9
|
import { parseArgs } from 'node:util';
|
|
10
10
|
const { values } = parseArgs({
|
|
11
11
|
options: {
|
|
12
|
-
mock: { type: 'boolean', default: false },
|
|
13
12
|
port: { type: 'string', default: '' },
|
|
14
13
|
help: { type: 'boolean', short: 'h', default: false },
|
|
15
14
|
},
|
|
@@ -24,13 +23,11 @@ Commands:
|
|
|
24
23
|
deploy Deploy as a Cloudflare Worker (run "deploy --help" for options)
|
|
25
24
|
|
|
26
25
|
Options:
|
|
27
|
-
--mock Run with mock data (no real host connection needed)
|
|
28
26
|
--port NUM Port to listen on (default: 3001, or PORT env var)
|
|
29
27
|
-h, --help Show this help message
|
|
30
28
|
|
|
31
29
|
Examples:
|
|
32
30
|
npx green-screen-proxy # Start proxy on port 3001
|
|
33
|
-
npx green-screen-proxy --mock # Start with mock screens
|
|
34
31
|
npx green-screen-proxy --port 8080 # Start on port 8080
|
|
35
32
|
npx green-screen-proxy deploy # Deploy to Cloudflare Workers`);
|
|
36
33
|
process.exit(0);
|
|
@@ -38,8 +35,5 @@ Examples:
|
|
|
38
35
|
if (values.port) {
|
|
39
36
|
process.env.PORT = values.port;
|
|
40
37
|
}
|
|
41
|
-
if (values.mock) {
|
|
42
|
-
process.argv.push('--mock');
|
|
43
|
-
}
|
|
44
38
|
await import('./server.js');
|
|
45
39
|
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,uDAAuD;AACvD,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACnC,IAAI,UAAU,KAAK,QAAQ,EAAE,CAAC;IAC5B,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;IAC/C,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC3B,OAAO,EAAE;QACP,IAAI,EAAE,EAAE,IAAI,EAAE,
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,uDAAuD;AACvD,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACnC,IAAI,UAAU,KAAK,QAAQ,EAAE,CAAC;IAC5B,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;IAC/C,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC3B,OAAO,EAAE;QACP,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE;QACrC,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE;KACtD;CACF,CAAC,CAAC;AAEH,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;yEAe2D,CAAC,CAAC;IACzE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;AACjC,CAAC;AAED,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC"}
|
package/dist/controller.d.ts
CHANGED
|
@@ -25,12 +25,26 @@ export declare class SessionController {
|
|
|
25
25
|
password?: string;
|
|
26
26
|
sessionId: string;
|
|
27
27
|
terminalType?: string;
|
|
28
|
+
/** EBCDIC code page override (e.g. 'cp290' for Japanese Katakana). */
|
|
29
|
+
codePage?: 'cp37' | 'cp290';
|
|
28
30
|
}): Promise<ProtocolHandler>;
|
|
31
|
+
/**
|
|
32
|
+
* Adopt an already-connected ProtocolHandler owned by another holder
|
|
33
|
+
* (e.g. a REST-created Session). Wires this controller up to dispatch
|
|
34
|
+
* key/text/setCursor commands against the existing handler without
|
|
35
|
+
* re-binding connection-level event listeners — the owning Session
|
|
36
|
+
* remains responsible for the handler lifecycle.
|
|
37
|
+
*
|
|
38
|
+
* Used by the WebSocket reattach path so WS clients can drive a session
|
|
39
|
+
* that was originally created via REST.
|
|
40
|
+
*/
|
|
41
|
+
adoptHandler(handler: ProtocolHandler): void;
|
|
29
42
|
handleText(text: string): void;
|
|
30
43
|
handleKey(key: string): Promise<void>;
|
|
31
44
|
handleSetCursor(row: number, col: number): void;
|
|
32
45
|
handleDisconnect(): void;
|
|
33
46
|
getScreenData(): ScreenData | null;
|
|
47
|
+
handleReadMdt(modifiedOnly: boolean): void;
|
|
34
48
|
private waitForScreen;
|
|
35
49
|
}
|
|
36
50
|
//# sourceMappingURL=controller.d.ts.map
|
package/dist/controller.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../src/controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,eAAe,EAAiB,MAAM,sBAAsB,CAAC;AAC7F,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAErE;;;;;;;GAOG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,EAAE,eAAe,GAAG,IAAI,CAAQ;IACvC,SAAS,EAAE,OAAO,CAAS;IAC3B,OAAO,CAAC,IAAI,CAAwB;gBAExB,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI;IAIvC;;;OAGG;IACG,aAAa,CAAC,IAAI,EAAE;QACxB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,YAAY,CAAC;QACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../src/controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,eAAe,EAAiB,MAAM,sBAAsB,CAAC;AAC7F,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAErE;;;;;;;GAOG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,EAAE,eAAe,GAAG,IAAI,CAAQ;IACvC,SAAS,EAAE,OAAO,CAAS;IAC3B,OAAO,CAAC,IAAI,CAAwB;gBAExB,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI;IAIvC;;;OAGG;IACG,aAAa,CAAC,IAAI,EAAE;QACxB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,YAAY,CAAC;QACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,sEAAsE;QACtE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;KAC7B,GAAG,OAAO,CAAC,eAAe,CAAC;IA+C5B;;;;;;;;;OASG;IACH,YAAY,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI;IAK5C,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IASxB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA0C3C,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAU/C,gBAAgB,IAAI,IAAI;IASxB,aAAa,IAAI,UAAU,GAAG,IAAI;IAIlC,aAAa,CAAC,YAAY,EAAE,OAAO,GAAG,IAAI;IAS1C,OAAO,CAAC,aAAa;CAUtB"}
|
package/dist/controller.js
CHANGED
|
@@ -24,7 +24,7 @@ export class SessionController {
|
|
|
24
24
|
this.handler = null;
|
|
25
25
|
this.connected = false;
|
|
26
26
|
}
|
|
27
|
-
const { host, port = 23, protocol = 'tn5250', username, password, sessionId, terminalType } = opts;
|
|
27
|
+
const { host, port = 23, protocol = 'tn5250', username, password, sessionId, terminalType, codePage } = opts;
|
|
28
28
|
this.handler = createProtocolHandler(protocol);
|
|
29
29
|
this.send({ type: 'status', data: { connected: false, status: 'connecting', protocol, host } });
|
|
30
30
|
// Bind protocol events → WebSocket messages
|
|
@@ -39,7 +39,10 @@ export class SessionController {
|
|
|
39
39
|
this.send({ type: 'error', message: err.message });
|
|
40
40
|
this.send({ type: 'status', data: { connected: false, status: 'error', protocol, host, error: err.message } });
|
|
41
41
|
});
|
|
42
|
-
|
|
42
|
+
const connectOpts = (terminalType || codePage)
|
|
43
|
+
? { ...(terminalType ? { terminalType } : {}), ...(codePage ? { codePage } : {}) }
|
|
44
|
+
: undefined;
|
|
45
|
+
await this.handler.connect(host, port, connectOpts);
|
|
43
46
|
this.connected = true;
|
|
44
47
|
this.send({ type: 'status', data: { connected: true, status: 'connected', protocol, host } });
|
|
45
48
|
// Auto-sign-in if credentials provided and handler supports it
|
|
@@ -56,6 +59,20 @@ export class SessionController {
|
|
|
56
59
|
this.send({ type: 'connected', sessionId });
|
|
57
60
|
return this.handler;
|
|
58
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Adopt an already-connected ProtocolHandler owned by another holder
|
|
64
|
+
* (e.g. a REST-created Session). Wires this controller up to dispatch
|
|
65
|
+
* key/text/setCursor commands against the existing handler without
|
|
66
|
+
* re-binding connection-level event listeners — the owning Session
|
|
67
|
+
* remains responsible for the handler lifecycle.
|
|
68
|
+
*
|
|
69
|
+
* Used by the WebSocket reattach path so WS clients can drive a session
|
|
70
|
+
* that was originally created via REST.
|
|
71
|
+
*/
|
|
72
|
+
adoptHandler(handler) {
|
|
73
|
+
this.handler = handler;
|
|
74
|
+
this.connected = true;
|
|
75
|
+
}
|
|
59
76
|
handleText(text) {
|
|
60
77
|
if (!this.handler || !this.connected) {
|
|
61
78
|
this.send({ type: 'error', message: 'Not connected' });
|
|
@@ -125,6 +142,14 @@ export class SessionController {
|
|
|
125
142
|
getScreenData() {
|
|
126
143
|
return this.handler?.getScreenData() ?? null;
|
|
127
144
|
}
|
|
145
|
+
handleReadMdt(modifiedOnly) {
|
|
146
|
+
if (!this.handler || !this.connected) {
|
|
147
|
+
this.send({ type: 'error', message: 'Not connected' });
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const fields = this.handler.readFieldValues(modifiedOnly);
|
|
151
|
+
this.send({ type: 'mdt', data: { modifiedOnly, fields } });
|
|
152
|
+
}
|
|
128
153
|
waitForScreen(timeoutMs) {
|
|
129
154
|
return new Promise((resolve) => {
|
|
130
155
|
if (!this.handler) {
|
package/dist/controller.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller.js","sourceRoot":"","sources":["../src/controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAmB,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAG7F;;;;;;;GAOG;AACH,MAAM,OAAO,iBAAiB;IAC5B,OAAO,GAA2B,IAAI,CAAC;IACvC,SAAS,GAAY,KAAK,CAAC;IACnB,IAAI,CAAwB;IAEpC,YAAY,IAA2B;QACrC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,aAAa,CAAC,
|
|
1
|
+
{"version":3,"file":"controller.js","sourceRoot":"","sources":["../src/controller.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
2
|
import { Server as HttpServer } from 'http';
|
|
3
|
+
export { type SessionStore, InMemorySessionStore, setSessionStore, getSessionStore, sessionLifecycle, } from './session-store.js';
|
|
3
4
|
export interface ProxyOptions {
|
|
4
5
|
/** Port to listen on (default: 3001) */
|
|
5
6
|
port?: number;
|
|
6
|
-
/** Use mock screens instead of real connections */
|
|
7
|
-
mock?: boolean;
|
|
8
7
|
}
|
|
9
8
|
export interface ProxyServer {
|
|
10
9
|
/** The underlying HTTP server */
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAE9B,OAAO,EAAgB,MAAM,IAAI,UAAU,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAE9B,OAAO,EAAgB,MAAM,IAAI,UAAU,EAAE,MAAM,MAAM,CAAC;AAK1D,OAAO,EACL,KAAK,YAAY,EACjB,oBAAoB,EACpB,eAAe,EACf,eAAe,EACf,gBAAgB,GACjB,MAAM,oBAAoB,CAAC;AAE5B,MAAM,WAAW,YAAY;IAC3B,wCAAwC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,iCAAiC;IACjC,MAAM,EAAE,UAAU,CAAC;IACnB,sBAAsB;IACtB,GAAG,EAAE,OAAO,CAAC,OAAO,CAAC;IACrB,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,sBAAsB;IACtB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,WAAW,CAAC,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,WAAW,CAAC,CAkDlF"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
2
|
import cors from 'cors';
|
|
3
3
|
import { createServer } from 'http';
|
|
4
|
+
// Re-export session store primitives so integrators can swap the default
|
|
5
|
+
// in-memory store for a custom implementation (e.g. Redis routing for
|
|
6
|
+
// multi-process deployments) before createProxy() is called.
|
|
7
|
+
export { InMemorySessionStore, setSessionStore, getSessionStore, sessionLifecycle, } from './session-store.js';
|
|
4
8
|
/**
|
|
5
9
|
* Create and start a green-screen proxy server.
|
|
6
10
|
*
|
|
@@ -16,23 +20,15 @@ import { createServer } from 'http';
|
|
|
16
20
|
* ```
|
|
17
21
|
*/
|
|
18
22
|
export async function createProxy(options = {}) {
|
|
19
|
-
const { port = 3001
|
|
23
|
+
const { port = 3001 } = options;
|
|
20
24
|
const app = express();
|
|
21
25
|
app.use(cors());
|
|
22
26
|
app.use(express.json());
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
else {
|
|
29
|
-
const [{ default: routes }, { setupWebSocket: setupWs }] = await Promise.all([
|
|
30
|
-
import('./routes.js'),
|
|
31
|
-
import('./websocket.js'),
|
|
32
|
-
]);
|
|
33
|
-
app.use('/', routes);
|
|
34
|
-
setupWebSocket = setupWs;
|
|
35
|
-
}
|
|
27
|
+
const [{ default: routes }, { setupWebSocket }] = await Promise.all([
|
|
28
|
+
import('./routes.js'),
|
|
29
|
+
import('./websocket.js'),
|
|
30
|
+
]);
|
|
31
|
+
app.use('/', routes);
|
|
36
32
|
const server = createServer(app);
|
|
37
33
|
let resolvedPort = port;
|
|
38
34
|
const maxPort = port + 20;
|
|
@@ -53,8 +49,7 @@ export async function createProxy(options = {}) {
|
|
|
53
49
|
server.listen(resolvedPort, () => {
|
|
54
50
|
// Attach WebSocket after successful listen to avoid EADDRINUSE
|
|
55
51
|
// being re-emitted as an unhandled error on the WebSocketServer
|
|
56
|
-
|
|
57
|
-
setupWebSocket(server);
|
|
52
|
+
setupWebSocket(server);
|
|
58
53
|
resolve({
|
|
59
54
|
server,
|
|
60
55
|
app,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,YAAY,EAAwB,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,YAAY,EAAwB,MAAM,MAAM,CAAC;AAE1D,yEAAyE;AACzE,sEAAsE;AACtE,6DAA6D;AAC7D,OAAO,EAEL,oBAAoB,EACpB,eAAe,EACf,eAAe,EACf,gBAAgB,GACjB,MAAM,oBAAoB,CAAC;AAkB5B;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,UAAwB,EAAE;IAC1D,MAAM,EAAE,IAAI,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC;IAEhC,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;IACtB,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IAChB,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAExB,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,cAAc,EAAE,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QAClE,MAAM,CAAC,aAAa,CAAC;QACrB,MAAM,CAAC,gBAAgB,CAAC;KACzB,CAAC,CAAC;IACH,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAErB,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IAEjC,IAAI,YAAY,GAAG,IAAI,CAAC;IACxB,MAAM,OAAO,GAAG,IAAI,GAAG,EAAE,CAAC;IAE1B,OAAO,IAAI,OAAO,CAAc,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAClD,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAA0B,EAAE,EAAE;YAChD,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;gBAC9B,YAAY,EAAE,CAAC;gBACf,IAAI,YAAY,GAAG,OAAO,EAAE,CAAC;oBAC3B,MAAM,CAAC,IAAI,KAAK,CAAC,aAAa,IAAI,IAAI,OAAO,aAAa,CAAC,CAAC,CAAC;oBAC7D,OAAO;gBACT,CAAC;gBACD,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;YAC9B,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,GAAG,CAAC,CAAC;YACd,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,GAAG,EAAE;YAC/B,+DAA+D;YAC/D,gEAAgE;YAChE,cAAc,CAAC,MAAM,CAAC,CAAC;YAEvB,OAAO,CAAC;gBACN,MAAM;gBACN,GAAG;gBACH,IAAI,EAAE,YAAY;gBAClB,KAAK;oBACH,OAAO,IAAI,OAAO,CAAO,CAAC,GAAG,EAAE,EAAE;wBAC/B,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;wBAC1B,UAAU,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;oBAChC,CAAC,CAAC,CAAC;gBACL,CAAC;aACF,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ProtocolHandler } from './types.js';
|
|
2
|
-
import type { ScreenData, ProtocolOptions, ProtocolType } from './types.js';
|
|
2
|
+
import type { ScreenData, ProtocolOptions, ProtocolType, FieldValue } from './types.js';
|
|
3
3
|
import { TN5250Connection } from '../tn5250/connection.js';
|
|
4
4
|
import { ScreenBuffer } from '../tn5250/screen.js';
|
|
5
5
|
import { TN5250Parser } from '../tn5250/parser.js';
|
|
@@ -19,6 +19,7 @@ export declare class TN5250Handler extends ProtocolHandler {
|
|
|
19
19
|
connect(host: string, port: number, options?: ProtocolOptions): Promise<void>;
|
|
20
20
|
disconnect(): void;
|
|
21
21
|
getScreenData(): ScreenData;
|
|
22
|
+
readFieldValues(modifiedOnly?: boolean): FieldValue[];
|
|
22
23
|
sendText(text: string): boolean;
|
|
23
24
|
sendKey(keyName: string): boolean;
|
|
24
25
|
/**
|
|
@@ -59,7 +60,7 @@ export declare class TN5250Handler extends ProtocolHandler {
|
|
|
59
60
|
/** Wait for the next screenChange event (or timeout with current screen). */
|
|
60
61
|
private waitForScreen;
|
|
61
62
|
/** Wait until the screen has at least `minFields` input fields, or timeout. */
|
|
62
|
-
|
|
63
|
+
waitForScreenWithFields(minFields: number, timeoutMs: number): Promise<ScreenData>;
|
|
63
64
|
/**
|
|
64
65
|
* Set cursor position (for click-to-position). Validates the target is
|
|
65
66
|
* inside an input field; if not, finds the nearest input field.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tn5250-handler.d.ts","sourceRoot":"","sources":["../../src/protocols/tn5250-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"tn5250-handler.d.ts","sourceRoot":"","sources":["../../src/protocols/tn5250-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxF,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAY,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGrD;;;GAGG;AACH,qBAAa,aAAc,SAAQ,eAAe;IAChD,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAY;IAE3C,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;;IAchC,IAAI,WAAW,IAAI,OAAO,CAEzB;IAEK,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAoCnF,UAAU,IAAI,IAAI;IAIlB,aAAa,IAAI,UAAU;IAI3B,eAAe,CAAC,YAAY,GAAE,OAAc,GAAG,UAAU,EAAE;IAI3D,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI/B,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IA2LjC;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;IAc1B,OAAO,CAAC,gBAAgB;IAcxB;;;;;OAKG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO;IAmDvD,iEAAiE;IACjE,OAAO,CAAC,WAAW,CAA6E;IAEhG;;;OAGG;IACH,OAAO,CAAC,eAAe;IAsBvB;;;;OAIG;IACH,aAAa,IAAI,IAAI;IAerB,OAAO,CAAC,YAAY;IAQpB;;;;OAIG;IACG,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IASvF,6EAA6E;IAC7E,OAAO,CAAC,aAAa;IAUrB,+EAA+E;IAC/E,uBAAuB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAuBlF;;;;OAIG;IACH,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO;IAyC5C,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAI3B,OAAO,IAAI,IAAI;IAKf,OAAO,CAAC,QAAQ;CAuBjB"}
|
|
@@ -38,6 +38,25 @@ export class TN5250Handler extends ProtocolHandler {
|
|
|
38
38
|
if (dims.rows !== this.screen.rows || dims.cols !== this.screen.cols) {
|
|
39
39
|
this.screen.resize(dims.rows, dims.cols);
|
|
40
40
|
}
|
|
41
|
+
// Resolve EBCDIC code page. Explicit option wins; otherwise derive from
|
|
42
|
+
// the terminal type string (IBM-5555-* is the standard Japanese DBCS
|
|
43
|
+
// terminal family) and fall back to CP37 for everything else.
|
|
44
|
+
if (options?.codePage) {
|
|
45
|
+
this.screen.codePage = options.codePage;
|
|
46
|
+
}
|
|
47
|
+
else if (/^IBM-5555/i.test(termType) || /KATAKANA/i.test(termType)) {
|
|
48
|
+
this.screen.codePage = 'cp290';
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
this.screen.codePage = 'cp37';
|
|
52
|
+
}
|
|
53
|
+
// For Japanese sessions, register the built-in DBCS table so
|
|
54
|
+
// hiragana/katakana/symbols render without further setup. A full
|
|
55
|
+
// Kanji table can be layered on top via `registerDbcsTable(...)`.
|
|
56
|
+
if (this.screen.codePage === 'cp290') {
|
|
57
|
+
const { registerBuiltinDbcsTable } = await import('../tn5250/ebcdic-jp-builtin.js');
|
|
58
|
+
registerBuiltinDbcsTable();
|
|
59
|
+
}
|
|
41
60
|
const connectTimeout = options?.connectTimeout;
|
|
42
61
|
await this.connection.connect(host, port, termType, connectTimeout);
|
|
43
62
|
}
|
|
@@ -47,6 +66,9 @@ export class TN5250Handler extends ProtocolHandler {
|
|
|
47
66
|
getScreenData() {
|
|
48
67
|
return this.screen.toScreenData();
|
|
49
68
|
}
|
|
69
|
+
readFieldValues(modifiedOnly = true) {
|
|
70
|
+
return this.screen.readFieldValues(modifiedOnly);
|
|
71
|
+
}
|
|
50
72
|
sendText(text) {
|
|
51
73
|
return this.encoder.insertText(text);
|
|
52
74
|
}
|
|
@@ -133,39 +155,68 @@ export class TN5250Handler extends ProtocolHandler {
|
|
|
133
155
|
return true;
|
|
134
156
|
}
|
|
135
157
|
// Tab/Backtab: move cursor to next/previous input field.
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
//
|
|
158
|
+
//
|
|
159
|
+
// Keep only fields with a native interactive attribute byte — either
|
|
160
|
+
// underscored (regular text input) or non-display (password input).
|
|
161
|
+
// This matches `isVisibleInput()` in screen.ts and excludes UIM framework
|
|
162
|
+
// artifact fields that are technically non-bypass but carry no visible
|
|
163
|
+
// interactive attribute (e.g. the selection field at (1,2) on the main
|
|
164
|
+
// menu produces "Type option number or command" error when navigated
|
|
165
|
+
// into). Falls back to the last input field if nothing matches.
|
|
166
|
+
//
|
|
167
|
+
// IMPORTANT: password fields on IBM i sign-on screens are non-display
|
|
168
|
+
// input fields by design (attribute byte lower bits = 0x07, so chars
|
|
169
|
+
// don't echo). Any filter that broadly excludes non-display fields
|
|
170
|
+
// will skip them on Tab and make sign-on impossible.
|
|
171
|
+
//
|
|
172
|
+
// Tab order: if ANY field has a non-zero `resequence` FCW (0x80xx), order
|
|
173
|
+
// fields by resequence ascending (resequence=0 → spatial), matching the
|
|
174
|
+
// IBM 5250 Functions Reference cursor progression rules. Otherwise fall
|
|
175
|
+
// back to pure spatial order. Per lib5250 session.c:1577-1579 (which
|
|
176
|
+
// stores the FCW but leaves sequencing as a FIXME).
|
|
141
177
|
if (normalizedKey === 'Tab' || normalizedKey === 'Backtab') {
|
|
142
|
-
const allInputs = this.screen.fields
|
|
143
|
-
.filter(f => this.screen.isInputField(f))
|
|
144
|
-
.sort((a, b) => this.screen.offset(a.row, a.col) - this.screen.offset(b.row, b.col));
|
|
178
|
+
const allInputs = this.screen.fields.filter(f => this.screen.isInputField(f));
|
|
145
179
|
if (allInputs.length === 0)
|
|
146
180
|
return false;
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
181
|
+
// Determine ordering: resequence-aware if any field declares it.
|
|
182
|
+
const hasResequence = allInputs.some(f => f.resequence && f.resequence > 0);
|
|
183
|
+
const orderOf = (f) => {
|
|
184
|
+
if (hasResequence) {
|
|
185
|
+
// Resequenced fields come first in FCW order; non-resequenced
|
|
186
|
+
// fields sort after them in spatial order. Spatial offset is
|
|
187
|
+
// added as a tiebreaker within the same resequence value.
|
|
188
|
+
const base = f.resequence && f.resequence > 0 ? f.resequence : 10000;
|
|
189
|
+
return base * 1_000_000 + this.screen.offset(f.row, f.col);
|
|
190
|
+
}
|
|
191
|
+
return this.screen.offset(f.row, f.col);
|
|
192
|
+
};
|
|
193
|
+
allInputs.sort((a, b) => orderOf(a) - orderOf(b));
|
|
194
|
+
// Keep fields that have a native underscore OR native non-display raw
|
|
195
|
+
// attribute byte — both are legitimate interactive targets. Drops
|
|
196
|
+
// UIM artifacts that have neither attribute set.
|
|
197
|
+
const functional = allInputs.filter(f => this.screen.hasNativeUnderscore(f) || this.screen.hasNativeNonDisplay(f));
|
|
154
198
|
const inputFields = functional.length > 0 ? functional
|
|
155
199
|
: [allInputs[allInputs.length - 1]];
|
|
200
|
+
// Find current field's index in the ordered list so we walk the
|
|
201
|
+
// resequenced chain even if the cursor isn't at an exact field start.
|
|
156
202
|
const cursorPos = this.screen.offset(this.screen.cursorRow, this.screen.cursorCol);
|
|
203
|
+
const curIdx = inputFields.findIndex(f => {
|
|
204
|
+
const start = this.screen.offset(f.row, f.col);
|
|
205
|
+
return cursorPos >= start && cursorPos < start + f.length;
|
|
206
|
+
});
|
|
207
|
+
let target;
|
|
157
208
|
if (normalizedKey === 'Tab') {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
this.screen.cursorCol = target.col;
|
|
209
|
+
target = curIdx >= 0 && curIdx + 1 < inputFields.length
|
|
210
|
+
? inputFields[curIdx + 1]
|
|
211
|
+
: inputFields[0];
|
|
162
212
|
}
|
|
163
213
|
else {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
this.screen.cursorCol = target.col;
|
|
214
|
+
target = curIdx > 0
|
|
215
|
+
? inputFields[curIdx - 1]
|
|
216
|
+
: inputFields[inputFields.length - 1];
|
|
168
217
|
}
|
|
218
|
+
this.screen.cursorRow = target.row;
|
|
219
|
+
this.screen.cursorCol = target.col;
|
|
169
220
|
return true;
|
|
170
221
|
}
|
|
171
222
|
// Field Exit: right-adjust field value, mark modified, advance to next field
|
|
@@ -173,10 +224,17 @@ export class TN5250Handler extends ProtocolHandler {
|
|
|
173
224
|
this.encoder.fieldExit();
|
|
174
225
|
return this.sendKey('Tab');
|
|
175
226
|
}
|
|
176
|
-
// Reset: clear keyboard lock and
|
|
227
|
+
// Reset: clear keyboard lock and restore message line (per lib5250
|
|
228
|
+
// display.c:1861-1876: clearing the INHIBIT indicator restores
|
|
229
|
+
// saved_msg_line). Client-side only — nothing sent to host.
|
|
177
230
|
if (normalizedKey === 'Reset') {
|
|
178
231
|
this.screen.keyboardLocked = false;
|
|
179
|
-
this.screen.
|
|
232
|
+
if (this.screen.savedMsgLine) {
|
|
233
|
+
this.screen.restoreMsgLine();
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
this.screen.clearErrorLine();
|
|
237
|
+
}
|
|
180
238
|
if (this.screen.savedCursorBeforeError) {
|
|
181
239
|
this.screen.cursorRow = this.screen.savedCursorBeforeError.row;
|
|
182
240
|
this.screen.cursorCol = this.screen.savedCursorBeforeError.col;
|