green-screen-proxy 1.1.1 → 1.2.0

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.
Files changed (91) hide show
  1. package/README.md +93 -8
  2. package/dist/cli.js +0 -6
  3. package/dist/cli.js.map +1 -1
  4. package/dist/controller.d.ts +3 -0
  5. package/dist/controller.d.ts.map +1 -1
  6. package/dist/controller.js +14 -5
  7. package/dist/controller.js.map +1 -1
  8. package/dist/index.d.ts +1 -2
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +18 -16
  11. package/dist/index.js.map +1 -1
  12. package/dist/protocols/tn3270-handler.d.ts +2 -1
  13. package/dist/protocols/tn3270-handler.d.ts.map +1 -1
  14. package/dist/protocols/tn3270-handler.js +10 -2
  15. package/dist/protocols/tn3270-handler.js.map +1 -1
  16. package/dist/protocols/tn5250-handler.d.ts +3 -2
  17. package/dist/protocols/tn5250-handler.d.ts.map +1 -1
  18. package/dist/protocols/tn5250-handler.js +85 -26
  19. package/dist/protocols/tn5250-handler.js.map +1 -1
  20. package/dist/protocols/types.d.ts +30 -2
  21. package/dist/protocols/types.d.ts.map +1 -1
  22. package/dist/protocols/types.js +32 -0
  23. package/dist/protocols/types.js.map +1 -1
  24. package/dist/routes.d.ts.map +1 -1
  25. package/dist/routes.js +261 -30
  26. package/dist/routes.js.map +1 -1
  27. package/dist/server.js +1 -5
  28. package/dist/server.js.map +1 -1
  29. package/dist/session-store.d.ts +68 -0
  30. package/dist/session-store.d.ts.map +1 -0
  31. package/dist/session-store.js +40 -0
  32. package/dist/session-store.js.map +1 -0
  33. package/dist/session.d.ts +32 -2
  34. package/dist/session.d.ts.map +1 -1
  35. package/dist/session.js +105 -9
  36. package/dist/session.js.map +1 -1
  37. package/dist/standalone.d.ts +3 -0
  38. package/dist/standalone.d.ts.map +1 -0
  39. package/dist/standalone.js +6 -0
  40. package/dist/standalone.js.map +1 -0
  41. package/dist/tn3270/connection.d.ts +1 -1
  42. package/dist/tn3270/connection.d.ts.map +1 -1
  43. package/dist/tn3270/connection.js +2 -2
  44. package/dist/tn3270/connection.js.map +1 -1
  45. package/dist/tn3270/screen.d.ts +1 -0
  46. package/dist/tn3270/screen.d.ts.map +1 -1
  47. package/dist/tn3270/screen.js +1 -0
  48. package/dist/tn3270/screen.js.map +1 -1
  49. package/dist/tn5250/connection.d.ts +6 -1
  50. package/dist/tn5250/connection.d.ts.map +1 -1
  51. package/dist/tn5250/connection.js +27 -3
  52. package/dist/tn5250/connection.js.map +1 -1
  53. package/dist/tn5250/constants.d.ts +39 -3
  54. package/dist/tn5250/constants.d.ts.map +1 -1
  55. package/dist/tn5250/constants.js +51 -3
  56. package/dist/tn5250/constants.js.map +1 -1
  57. package/dist/tn5250/ebcdic-jp-builtin.d.ts +45 -0
  58. package/dist/tn5250/ebcdic-jp-builtin.d.ts.map +1 -0
  59. package/dist/tn5250/ebcdic-jp-builtin.js +124 -0
  60. package/dist/tn5250/ebcdic-jp-builtin.js.map +1 -0
  61. package/dist/tn5250/ebcdic-jp.d.ts +61 -0
  62. package/dist/tn5250/ebcdic-jp.d.ts.map +1 -0
  63. package/dist/tn5250/ebcdic-jp.js +188 -0
  64. package/dist/tn5250/ebcdic-jp.js.map +1 -0
  65. package/dist/tn5250/ebcdic.d.ts +13 -4
  66. package/dist/tn5250/ebcdic.d.ts.map +1 -1
  67. package/dist/tn5250/ebcdic.js +30 -8
  68. package/dist/tn5250/ebcdic.js.map +1 -1
  69. package/dist/tn5250/encoder.d.ts +41 -9
  70. package/dist/tn5250/encoder.d.ts.map +1 -1
  71. package/dist/tn5250/encoder.js +228 -41
  72. package/dist/tn5250/encoder.js.map +1 -1
  73. package/dist/tn5250/parser.d.ts +14 -0
  74. package/dist/tn5250/parser.d.ts.map +1 -1
  75. package/dist/tn5250/parser.js +428 -53
  76. package/dist/tn5250/parser.js.map +1 -1
  77. package/dist/tn5250/screen.d.ts +144 -24
  78. package/dist/tn5250/screen.d.ts.map +1 -1
  79. package/dist/tn5250/screen.js +245 -13
  80. package/dist/tn5250/screen.js.map +1 -1
  81. package/dist/ui/assets/index-B51sr7HL.js +56 -0
  82. package/dist/ui/assets/index-B9wpEWAh.css +1 -0
  83. package/dist/ui/assets/index-BrUnECmE.css +1 -0
  84. package/dist/ui/assets/index-CDBbEXbH.js +56 -0
  85. package/dist/ui/index.html +16 -0
  86. package/dist/websocket.d.ts +3 -0
  87. package/dist/websocket.d.ts.map +1 -1
  88. package/dist/websocket.js +90 -1
  89. package/dist/websocket.js.map +1 -1
  90. package/dist/worker/index.js +5573 -0
  91. package/package.json +1 -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
- ### Mock mode
19
+ ### Connecting to a host
21
20
 
22
- With `--mock`, the proxy serves mock terminal screens useful for trying out the component without a real host connection.
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,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;QACzC,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;;;;;;;;;;;;;;;;;yEAiB2D,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,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;IAChB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AAC9B,CAAC;AAED,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC"}
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"}
@@ -25,12 +25,15 @@ 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>;
29
31
  handleText(text: string): void;
30
32
  handleKey(key: string): Promise<void>;
31
33
  handleSetCursor(row: number, col: number): void;
32
34
  handleDisconnect(): void;
33
35
  getScreenData(): ScreenData | null;
36
+ handleReadMdt(modifiedOnly: boolean): void;
34
37
  private waitForScreen;
35
38
  }
36
39
  //# sourceMappingURL=controller.d.ts.map
@@ -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;KACvB,GAAG,OAAO,CAAC,eAAe,CAAC;IA4C5B,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;IAY/C,gBAAgB,IAAI,IAAI;IASxB,aAAa,IAAI,UAAU,GAAG,IAAI;IAIlC,OAAO,CAAC,aAAa;CAUtB"}
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,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"}
@@ -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
- await this.handler.connect(host, port, terminalType ? { terminalType } : undefined);
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
@@ -110,9 +113,7 @@ export class SessionController {
110
113
  this.send({ type: 'error', message: 'Not connected' });
111
114
  return;
112
115
  }
113
- if (this.handler instanceof TN5250Handler) {
114
- this.handler.setCursor(row, col);
115
- }
116
+ this.handler.setCursor(row, col);
116
117
  const sd = this.handler.getScreenData();
117
118
  this.send({ type: 'cursor', data: { cursor_row: sd.cursor_row, cursor_col: sd.cursor_col } });
118
119
  }
@@ -127,6 +128,14 @@ export class SessionController {
127
128
  getScreenData() {
128
129
  return this.handler?.getScreenData() ?? null;
129
130
  }
131
+ handleReadMdt(modifiedOnly) {
132
+ if (!this.handler || !this.connected) {
133
+ this.send({ type: 'error', message: 'Not connected' });
134
+ return;
135
+ }
136
+ const fields = this.handler.readFieldValues(modifiedOnly);
137
+ this.send({ type: 'mdt', data: { modifiedOnly, fields } });
138
+ }
130
139
  waitForScreen(timeoutMs) {
131
140
  return new Promise((resolve) => {
132
141
  if (!this.handler) {
@@ -1 +1 @@
1
- {"version":3,"file":"controller.js","sourceRoot":"","sources":["../src/controller.ts"],"names":[],"mappings":"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"}
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 */
@@ -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;AAE1D,MAAM,WAAW,YAAY;IAC3B,wCAAwC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mDAAmD;IACnD,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;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"}
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,31 +20,26 @@ import { createServer } from 'http';
16
20
  * ```
17
21
  */
18
22
  export async function createProxy(options = {}) {
19
- const { port = 3001, mock = false } = options;
23
+ const { port = 3001 } = options;
20
24
  const app = express();
21
25
  app.use(cors());
22
26
  app.use(express.json());
23
- let setupWebSocket;
24
- if (mock) {
25
- const { default: mockRoutes } = await import('./mock/mock-routes.js');
26
- app.use('/', mockRoutes);
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
- if (setupWebSocket)
38
- setupWebSocket(server);
39
33
  let resolvedPort = port;
34
+ const maxPort = port + 20;
40
35
  return new Promise((resolve, reject) => {
41
36
  server.on('error', (err) => {
42
37
  if (err.code === 'EADDRINUSE') {
43
38
  resolvedPort++;
39
+ if (resolvedPort > maxPort) {
40
+ reject(new Error(`All ports ${port}–${maxPort} are in use`));
41
+ return;
42
+ }
44
43
  server.listen(resolvedPort);
45
44
  }
46
45
  else {
@@ -48,6 +47,9 @@ export async function createProxy(options = {}) {
48
47
  }
49
48
  });
50
49
  server.listen(resolvedPort, () => {
50
+ // Attach WebSocket after successful listen to avoid EADDRINUSE
51
+ // being re-emitted as an unhandled error on the WebSocketServer
52
+ setupWebSocket(server);
51
53
  resolve({
52
54
  server,
53
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;AAoB1D;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,UAAwB,EAAE;IAC1D,MAAM,EAAE,IAAI,GAAG,IAAI,EAAE,IAAI,GAAG,KAAK,EAAE,GAAG,OAAO,CAAC;IAE9C,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,IAAI,cAA0D,CAAC;IAE/D,IAAI,IAAI,EAAE,CAAC;QACT,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,uBAAuB,CAAC,CAAC;QACtE,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;IAC3B,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YAC3E,MAAM,CAAC,aAAa,CAAC;YACrB,MAAM,CAAC,gBAAgB,CAAC;SACzB,CAAC,CAAC;QACH,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QACrB,cAAc,GAAG,OAAO,CAAC;IAC3B,CAAC;IAED,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,cAAc;QAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IAE3C,IAAI,YAAY,GAAG,IAAI,CAAC;IAExB,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,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,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
+ {"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"}
@@ -16,11 +16,12 @@ export declare class TN3270Handler extends ProtocolHandler {
16
16
  readonly encoder: TN3270Encoder;
17
17
  constructor();
18
18
  get isConnected(): boolean;
19
- connect(host: string, port: number, _options?: ProtocolOptions): Promise<void>;
19
+ connect(host: string, port: number, options?: ProtocolOptions): Promise<void>;
20
20
  disconnect(): void;
21
21
  getScreenData(): ScreenData;
22
22
  sendText(text: string): boolean;
23
23
  sendKey(keyName: string): boolean;
24
+ setCursor(row: number, col: number): boolean;
24
25
  sendRaw(data: Buffer): void;
25
26
  destroy(): void;
26
27
  private onRecord;
@@ -1 +1 @@
1
- {"version":3,"file":"tn3270-handler.d.ts","sourceRoot":"","sources":["../../src/protocols/tn3270-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;AAC5E,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD;;;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,gBAAgB,CAAC;IAClC,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,QAAQ,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAIpF,UAAU,IAAI,IAAI;IAIlB,aAAa,IAAI,UAAU;IAI3B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI/B,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAOjC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAI3B,OAAO,IAAI,IAAI;IAKf,OAAO,CAAC,QAAQ;CAMjB"}
1
+ {"version":3,"file":"tn3270-handler.d.ts","sourceRoot":"","sources":["../../src/protocols/tn3270-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;AAC5E,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD;;;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,gBAAgB,CAAC;IAClC,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;IAKnF,UAAU,IAAI,IAAI;IAIlB,aAAa,IAAI,UAAU;IAI3B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI/B,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAOjC,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO;IAQ5C,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAI3B,OAAO,IAAI,IAAI;IAKf,OAAO,CAAC,QAAQ;CAMjB"}
@@ -26,8 +26,9 @@ export class TN3270Handler extends ProtocolHandler {
26
26
  get isConnected() {
27
27
  return this.connection.isConnected;
28
28
  }
29
- async connect(host, port, _options) {
30
- await this.connection.connect(host, port);
29
+ async connect(host, port, options) {
30
+ const connectTimeout = options?.connectTimeout;
31
+ await this.connection.connect(host, port, connectTimeout);
31
32
  }
32
33
  disconnect() {
33
34
  this.connection.disconnect();
@@ -45,6 +46,13 @@ export class TN3270Handler extends ProtocolHandler {
45
46
  this.connection.sendRaw(response);
46
47
  return true;
47
48
  }
49
+ setCursor(row, col) {
50
+ if (row < 0 || row >= this.screen.rows || col < 0 || col >= this.screen.cols) {
51
+ return false;
52
+ }
53
+ this.screen.cursorAddr = row * this.screen.cols + col;
54
+ return true;
55
+ }
48
56
  sendRaw(data) {
49
57
  this.connection.sendRaw(data);
50
58
  }
@@ -1 +1 @@
1
- {"version":3,"file":"tn3270-handler.js","sourceRoot":"","sources":["../../src/protocols/tn3270-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD;;;GAGG;AACH,MAAM,OAAO,aAAc,SAAQ,eAAe;IACvC,QAAQ,GAAiB,QAAQ,CAAC;IAElC,UAAU,CAAmB;IAC7B,MAAM,CAAmB;IACzB,MAAM,CAAe;IACrB,OAAO,CAAgB;IAEhC;QACE,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,MAAM,GAAG,IAAI,gBAAgB,EAAE,CAAC;QACrC,IAAI,CAAC,UAAU,GAAG,IAAI,gBAAgB,EAAE,CAAC;QACzC,IAAI,CAAC,MAAM,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC5C,IAAI,CAAC,OAAO,GAAG,IAAI,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAE9C,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,MAAc,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;QACtE,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,cAAc,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC;QACpE,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAU,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;IACvE,CAAC;IAED,IAAI,WAAW;QACb,OAAO,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC;IACrC,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,IAAY,EAAE,IAAY,EAAE,QAA0B;QAClE,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC5C,CAAC;IAED,UAAU;QACR,IAAI,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC;IAC/B,CAAC;IAED,aAAa;QACX,OAAO,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;IACpC,CAAC;IAED,QAAQ,CAAC,IAAY;QACnB,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;IACvC,CAAC;IAED,OAAO,CAAC,OAAe;QACrB,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;QACxD,IAAI,CAAC,QAAQ;YAAE,OAAO,KAAK,CAAC;QAC5B,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAClC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,CAAC,IAAY;QAClB,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,CAAC;IAED,OAAO;QACL,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,IAAI,CAAC,kBAAkB,EAAE,CAAC;IAC5B,CAAC;IAEO,QAAQ,CAAC,MAAc;QAC7B,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QACjD,IAAI,QAAQ,EAAE,CAAC;YACb,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC;QACxD,CAAC;IACH,CAAC;CACF"}
1
+ {"version":3,"file":"tn3270-handler.js","sourceRoot":"","sources":["../../src/protocols/tn3270-handler.ts"],"names":[],"mappings":"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"}
@@ -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
- private waitForScreenWithFields;
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;AAC5E,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;IAgBnF,UAAU,IAAI,IAAI;IAIlB,aAAa,IAAI,UAAU;IAI3B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI/B,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAsJjC;;;;;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,OAAO,CAAC,uBAAuB;IAuB/B;;;;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"}
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,7 +38,27 @@ 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
- await this.connection.connect(host, port, termType);
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
+ }
60
+ const connectTimeout = options?.connectTimeout;
61
+ await this.connection.connect(host, port, termType, connectTimeout);
42
62
  }
43
63
  disconnect() {
44
64
  this.connection.disconnect();
@@ -46,6 +66,9 @@ export class TN5250Handler extends ProtocolHandler {
46
66
  getScreenData() {
47
67
  return this.screen.toScreenData();
48
68
  }
69
+ readFieldValues(modifiedOnly = true) {
70
+ return this.screen.readFieldValues(modifiedOnly);
71
+ }
49
72
  sendText(text) {
50
73
  return this.encoder.insertText(text);
51
74
  }
@@ -132,39 +155,68 @@ export class TN5250Handler extends ProtocolHandler {
132
155
  return true;
133
156
  }
134
157
  // Tab/Backtab: move cursor to next/previous input field.
135
- // Filter out UIM framework artifact fields that are technically non-bypass
136
- // but not functional for user input (e.g. the selection field at (1,2) on
137
- // the main menu produces "Type option number or command" error when used).
138
- // Keep fields with native underscore/non-display (real interactive fields).
139
- // Fall back to all input fields if none match the filter.
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).
140
177
  if (normalizedKey === 'Tab' || normalizedKey === 'Backtab') {
141
- const allInputs = this.screen.fields
142
- .filter(f => this.screen.isInputField(f))
143
- .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));
144
179
  if (allInputs.length === 0)
145
180
  return false;
146
- // Exclude non-display fields (like UIM artifacts with rawAttr 0x27) —
147
- // they appear as invisible input areas that produce errors when used.
148
- // All other input fields (underscored, normal display, etc.) are kept.
149
- const functional = allInputs.filter(f => !this.screen.hasNativeNonDisplay(f));
150
- // If no fields have visible input indicators, use only the last input
151
- // field (typically the command line). UIM artifact fields earlier in the
152
- // list are not functional typing in them produces errors.
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));
153
198
  const inputFields = functional.length > 0 ? functional
154
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.
155
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;
156
208
  if (normalizedKey === 'Tab') {
157
- const next = inputFields.find(f => this.screen.offset(f.row, f.col) > cursorPos);
158
- const target = next || inputFields[0];
159
- this.screen.cursorRow = target.row;
160
- this.screen.cursorCol = target.col;
209
+ target = curIdx >= 0 && curIdx + 1 < inputFields.length
210
+ ? inputFields[curIdx + 1]
211
+ : inputFields[0];
161
212
  }
162
213
  else {
163
- const prev = [...inputFields].reverse().find(f => this.screen.offset(f.row, f.col) < cursorPos);
164
- const target = prev || inputFields[inputFields.length - 1];
165
- this.screen.cursorRow = target.row;
166
- this.screen.cursorCol = target.col;
214
+ target = curIdx > 0
215
+ ? inputFields[curIdx - 1]
216
+ : inputFields[inputFields.length - 1];
167
217
  }
218
+ this.screen.cursorRow = target.row;
219
+ this.screen.cursorCol = target.col;
168
220
  return true;
169
221
  }
170
222
  // Field Exit: right-adjust field value, mark modified, advance to next field
@@ -172,10 +224,17 @@ export class TN5250Handler extends ProtocolHandler {
172
224
  this.encoder.fieldExit();
173
225
  return this.sendKey('Tab');
174
226
  }
175
- // Reset: clear keyboard lock and error line (client-side only, nothing sent to host)
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.
176
230
  if (normalizedKey === 'Reset') {
177
231
  this.screen.keyboardLocked = false;
178
- this.screen.clearErrorLine();
232
+ if (this.screen.savedMsgLine) {
233
+ this.screen.restoreMsgLine();
234
+ }
235
+ else {
236
+ this.screen.clearErrorLine();
237
+ }
179
238
  if (this.screen.savedCursorBeforeError) {
180
239
  this.screen.cursorRow = this.screen.savedCursorBeforeError.row;
181
240
  this.screen.cursorCol = this.screen.savedCursorBeforeError.col;