green-screen-proxy 1.2.1 → 1.2.3

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.
@@ -43,6 +43,13 @@ export declare class SessionController {
43
43
  handleKey(key: string): Promise<void>;
44
44
  handleSetCursor(row: number, col: number): void;
45
45
  handleDisconnect(): void;
46
+ /**
47
+ * User-initiated graceful disconnect. If the protocol supports it
48
+ * (TN5250), types SIGNOFF on the command line and waits briefly for the
49
+ * host to end the interactive job before dropping the TCP socket. Avoids
50
+ * CPF1220 "device session limit" on IBM i with LMTDEVSSN=*YES.
51
+ */
52
+ handleGracefulDisconnect(timeoutMs?: number): Promise<void>;
46
53
  getScreenData(): ScreenData | null;
47
54
  handleReadMdt(modifiedOnly: boolean): void;
48
55
  private waitForScreen;
@@ -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;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"}
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;IAqD5B;;;;;;;;;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;;;;;OAKG;IACG,wBAAwB,CAAC,SAAS,GAAE,MAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAWvE,aAAa,IAAI,UAAU,GAAG,IAAI;IAIlC,aAAa,CAAC,YAAY,EAAE,OAAO,GAAG,IAAI;IAS1C,OAAO,CAAC,aAAa;CAUtB"}
@@ -47,9 +47,15 @@ export class SessionController {
47
47
  this.send({ type: 'status', data: { connected: true, status: 'connected', protocol, host } });
48
48
  // Auto-sign-in if credentials provided and handler supports it
49
49
  if (username && password && this.handler instanceof TN5250Handler) {
50
- const screen = await this.handler.performAutoSignIn(username, password);
51
- if (screen) {
52
- this.send({ type: 'screen', data: screen });
50
+ const result = await this.handler.performAutoSignIn(username, password);
51
+ if (result) {
52
+ this.send({ type: 'screen', data: result.screen });
53
+ if (result.authenticated) {
54
+ // Flip status so subsequent graceful-disconnect attempts will
55
+ // try a SIGNOFF. Failed sign-in (still on sign-on screen with
56
+ // CPF error) leaves status as 'connected'.
57
+ this.send({ type: 'status', data: { connected: true, status: 'authenticated', protocol, host, username } });
58
+ }
53
59
  }
54
60
  }
55
61
  else {
@@ -139,6 +145,23 @@ export class SessionController {
139
145
  this.connected = false;
140
146
  this.send({ type: 'status', data: { connected: false, status: 'disconnected' } });
141
147
  }
148
+ /**
149
+ * User-initiated graceful disconnect. If the protocol supports it
150
+ * (TN5250), types SIGNOFF on the command line and waits briefly for the
151
+ * host to end the interactive job before dropping the TCP socket. Avoids
152
+ * CPF1220 "device session limit" on IBM i with LMTDEVSSN=*YES.
153
+ */
154
+ async handleGracefulDisconnect(timeoutMs = 1500) {
155
+ if (this.handler && typeof this.handler.attemptSignOff === 'function') {
156
+ try {
157
+ await this.handler.attemptSignOff(timeoutMs);
158
+ }
159
+ catch {
160
+ // best-effort
161
+ }
162
+ }
163
+ this.handleDisconnect();
164
+ }
142
165
  getScreenData() {
143
166
  return this.handler?.getScreenData() ?? null;
144
167
  }
@@ -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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"}
@@ -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;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"}
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,CAsElF"}
package/dist/index.js CHANGED
@@ -24,7 +24,7 @@ export async function createProxy(options = {}) {
24
24
  const app = express();
25
25
  app.use(cors());
26
26
  app.use(express.json());
27
- const [{ default: routes }, { setupWebSocket }] = await Promise.all([
27
+ const [{ default: routes }, { setupWebSocket, shutdownAllWsControllers }] = await Promise.all([
28
28
  import('./routes.js'),
29
29
  import('./websocket.js'),
30
30
  ]);
@@ -54,10 +54,33 @@ export async function createProxy(options = {}) {
54
54
  server,
55
55
  app,
56
56
  port: resolvedPort,
57
- close() {
58
- return new Promise((res) => {
57
+ async close() {
58
+ // Drain live host connections BEFORE closing the HTTP server so
59
+ // each TCP socket has a chance to send a clean FIN upstream.
60
+ // Without this, abrupt process termination leaves host-side
61
+ // resources dangling (e.g. IBM i virtual telnet device
62
+ // descriptions stuck in VARY ON PENDING, blocking device-
63
+ // restricted user profiles from signing on again).
64
+ try {
65
+ // 1. REST-created sessions tracked by the session store.
66
+ const { getSessionStore } = await import('./session-store.js');
67
+ for (const session of Array.from(getSessionStore().values())) {
68
+ try {
69
+ session.destroy();
70
+ }
71
+ catch { /* ignore */ }
72
+ }
73
+ // 2. WS-created SessionControllers (live + orphaned).
74
+ shutdownAllWsControllers();
75
+ }
76
+ catch { /* ignore */ }
77
+ await new Promise((res) => {
59
78
  server.close(() => res());
60
- setTimeout(() => res(), 1000);
79
+ // Safety timeout — give in-flight HTTP requests up to 5s to
80
+ // finish before forcing exit. Session TCP FINs are sent
81
+ // synchronously above, so the host has already started its
82
+ // cleanup by the time this fires.
83
+ setTimeout(() => res(), 5000);
61
84
  });
62
85
  },
63
86
  });
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;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
+ {"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,wBAAwB,EAAE,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QAC5F,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,CAAC,KAAK;oBACT,gEAAgE;oBAChE,6DAA6D;oBAC7D,4DAA4D;oBAC5D,uDAAuD;oBACvD,0DAA0D;oBAC1D,mDAAmD;oBACnD,IAAI,CAAC;wBACH,yDAAyD;wBACzD,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;wBAC/D,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC;4BAC7D,IAAI,CAAC;gCAAC,OAAO,CAAC,OAAO,EAAE,CAAC;4BAAC,CAAC;4BAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;wBACnD,CAAC;wBACD,sDAAsD;wBACtD,wBAAwB,EAAE,CAAC;oBAC7B,CAAC;oBAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;oBAExB,MAAM,IAAI,OAAO,CAAO,CAAC,GAAG,EAAE,EAAE;wBAC9B,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;wBAC1B,4DAA4D;wBAC5D,wDAAwD;wBACxD,2DAA2D;wBAC3D,kCAAkC;wBAClC,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"}
@@ -18,6 +18,20 @@ export declare class TN5250Handler extends ProtocolHandler {
18
18
  get isConnected(): boolean;
19
19
  connect(host: string, port: number, options?: ProtocolOptions): Promise<void>;
20
20
  disconnect(): void;
21
+ /**
22
+ * Best-effort graceful sign-off. Types `SIGNOFF` on the screen's widest
23
+ * input field (which is almost always the command line on IBM i menus)
24
+ * and sends Enter, then waits briefly for the host to tear down the
25
+ * interactive job. On a bare sign-on screen there is no command line to
26
+ * type into — callers should only invoke this for authenticated sessions.
27
+ * Returns true if a SIGNOFF command was sent, false if nothing was done.
28
+ *
29
+ * This avoids CPF1220 "device session limit reached" errors caused by
30
+ * leaving the TN5250 TCP socket to rot: IBM i reaps the device job
31
+ * almost immediately once it sees the host-side SIGNOFF, whereas a bare
32
+ * TCP FIN leaves the job hanging until QDEVRCYACN picks it up.
33
+ */
34
+ attemptSignOff(timeoutMs?: number): Promise<boolean>;
21
35
  getScreenData(): ScreenData;
22
36
  readFieldValues(modifiedOnly?: boolean): FieldValue[];
23
37
  sendText(text: string): boolean;
@@ -54,17 +68,27 @@ export declare class TN5250Handler extends ProtocolHandler {
54
68
  /**
55
69
  * Full auto-sign-in flow: wait for sign-in fields, fill credentials,
56
70
  * submit, wait for confirmation screen, and restore field values.
57
- * Returns the final screen data, or null if sign-in fields weren't found.
71
+ * Returns { screen, authenticated } `authenticated` is true only if
72
+ * the host accepted the credentials (no longer on the sign-on screen).
73
+ * Returns null if the sign-on fields weren't found at all.
58
74
  */
59
- performAutoSignIn(username: string, password: string): Promise<ScreenData | null>;
75
+ performAutoSignIn(username: string, password: string): Promise<{
76
+ screen: ScreenData;
77
+ authenticated: boolean;
78
+ } | null>;
60
79
  /** Wait for the next screenChange event (or timeout with current screen). */
61
80
  private waitForScreen;
62
81
  /** Wait until the screen has at least `minFields` input fields, or timeout. */
63
82
  waitForScreenWithFields(minFields: number, timeoutMs: number): Promise<ScreenData>;
64
83
  /**
65
- * Set cursor position (for click-to-position). Validates the target is
66
- * inside an input field; if not, finds the nearest input field.
67
- * Returns true if the cursor was repositioned.
84
+ * Set cursor position (for click-to-position). Moves the cursor freely
85
+ * to the requested cell, matching lib5250 dbuffer.c:485-497 behaviour
86
+ * no field snapping. Typing at a non-field cell is separately inhibited
87
+ * by the encoder. Returns true always (the click landed somewhere).
88
+ *
89
+ * This is required for list screens like WRKSPLF: the user must be able
90
+ * to click on any row's Opt column, even though only one SF input field
91
+ * covers the entire list area.
68
92
  */
69
93
  setCursor(row: number, col: number): boolean;
70
94
  sendRaw(data: Buffer): void;
@@ -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,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"}
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;;;;;;;;;;;;OAYG;IACG,cAAc,CAAC,SAAS,GAAE,MAAa,GAAG,OAAO,CAAC,OAAO,CAAC;IA2DhE,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;IAoKjC;;;;;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;;;;;;OAMG;IACG,iBAAiB,CACrB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;QAAE,MAAM,EAAE,UAAU,CAAC;QAAC,aAAa,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IAoDjE,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;;;;;;;;;OASG;IACH,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;CAkCjB"}
@@ -63,6 +63,76 @@ export class TN5250Handler extends ProtocolHandler {
63
63
  disconnect() {
64
64
  this.connection.disconnect();
65
65
  }
66
+ /**
67
+ * Best-effort graceful sign-off. Types `SIGNOFF` on the screen's widest
68
+ * input field (which is almost always the command line on IBM i menus)
69
+ * and sends Enter, then waits briefly for the host to tear down the
70
+ * interactive job. On a bare sign-on screen there is no command line to
71
+ * type into — callers should only invoke this for authenticated sessions.
72
+ * Returns true if a SIGNOFF command was sent, false if nothing was done.
73
+ *
74
+ * This avoids CPF1220 "device session limit reached" errors caused by
75
+ * leaving the TN5250 TCP socket to rot: IBM i reaps the device job
76
+ * almost immediately once it sees the host-side SIGNOFF, whereas a bare
77
+ * TCP FIN leaves the job hanging until QDEVRCYACN picks it up.
78
+ */
79
+ async attemptSignOff(timeoutMs = 1500) {
80
+ if (!this.connection.isConnected)
81
+ return false;
82
+ // Skip if we're still on the sign-on screen (a genuine password
83
+ // NON_DISPLAY field AND the "Sign On" title text). Typing SIGNOFF
84
+ // into the User field would just burn another failed-login attempt.
85
+ // A plain NON_DISPLAY check alone would be fooled by UIM artifact
86
+ // fields on post-sign-on info screens.
87
+ const hasPasswordField = this.screen.fields.some((f) => this.screen.isInputField(f) && this.screen.hasNativeNonDisplay(f));
88
+ let hasSignOnTitle = false;
89
+ if (hasPasswordField) {
90
+ const rowLen = this.screen.cols;
91
+ for (let r = 0; r < Math.min(3, this.screen.rows); r++) {
92
+ const start = r * rowLen;
93
+ const line = this.screen.buffer.slice(start, start + rowLen).join('');
94
+ if (line.includes('Sign On')) {
95
+ hasSignOnTitle = true;
96
+ break;
97
+ }
98
+ }
99
+ }
100
+ if (hasPasswordField && hasSignOnTitle)
101
+ return false;
102
+ // Pick the widest non-password input field — on IBM i menus the
103
+ // command line is consistently the longest input. Avoids typing
104
+ // SIGNOFF into a small opt column or into a password field. We do
105
+ // NOT require the field to have the UNDERSCORE attribute: many IBM i
106
+ // panels render the command line via a colored/extended attribute
107
+ // instead of the bare 0x24 byte, so hasNativeUnderscore() would miss
108
+ // it.
109
+ const inputs = this.screen.fields.filter((f) => this.screen.isInputField(f) && !this.screen.hasNativeNonDisplay(f));
110
+ if (inputs.length === 0)
111
+ return false;
112
+ const target = inputs.reduce((a, b) => (b.length > a.length ? b : a));
113
+ if (target.length < 7)
114
+ return false; // "SIGNOFF" is 7 chars
115
+ this.screen.cursorRow = target.row;
116
+ this.screen.cursorCol = target.col;
117
+ if (!this.encoder.insertText('SIGNOFF'))
118
+ return false;
119
+ const enterAid = this.encoder.buildAidResponse('Enter');
120
+ if (!enterAid)
121
+ return false;
122
+ // Wait for the host's response to the SIGNOFF command. The host will
123
+ // either send us back to a sign-on screen or close the TCP connection.
124
+ // Either way, we've done our part once this resolves or times out.
125
+ const done = new Promise((resolve) => {
126
+ const timer = setTimeout(resolve, timeoutMs);
127
+ const onDisc = () => { clearTimeout(timer); resolve(); };
128
+ const onChange = () => { clearTimeout(timer); resolve(); };
129
+ this.connection.once('disconnected', onDisc);
130
+ this.once('screenChange', onChange);
131
+ });
132
+ this.connection.sendRaw(enterAid);
133
+ await done;
134
+ return true;
135
+ }
66
136
  getScreenData() {
67
137
  return this.screen.toScreenData();
68
138
  }
@@ -76,35 +146,40 @@ export class TN5250Handler extends ProtocolHandler {
76
146
  // Normalize key names: frontend sends uppercase (ENTER, TAB) but
77
147
  // KEY_TO_AID uses mixed case (Enter, PageUp). Handle both.
78
148
  const normalizedKey = this.normalizeKeyName(keyName);
79
- // Arrow keys: local cursor movement within input fields.
80
- // Left/Right: move within current field, stop at field boundaries.
81
- // Up/Down: move to previous/next input field.
149
+ // Arrow keys: move cursor one cell freely on the screen, with wrap at
150
+ // edges. Matches lib5250 dbuffer.c:591-640 (dbuffer_up/down/left/right)
151
+ // exactly arrow keys do NOT care about fields. Typing outside a
152
+ // non-bypass field is separately inhibited by the encoder.
153
+ //
154
+ // This is essential for list screens like WRKSPLF: the host defines a
155
+ // single Opt input field at the first visible row, and the user arrows
156
+ // down through the column to the row they want to act on. The cursor
157
+ // position at Enter-time is what the host uses to know which row.
82
158
  if (normalizedKey === 'ArrowLeft' || normalizedKey === 'ArrowRight' ||
83
159
  normalizedKey === 'ArrowUp' || normalizedKey === 'ArrowDown') {
84
- const field = this.screen.getFieldAtCursor();
85
- if (!field || !this.screen.isInputField(field))
86
- return true; // no-op outside input
160
+ const rows = this.screen.rows;
161
+ const cols = this.screen.cols;
87
162
  if (normalizedKey === 'ArrowLeft') {
88
- if (this.screen.cursorCol > field.col) {
89
- this.screen.cursorCol--;
163
+ if (--this.screen.cursorCol < 0) {
164
+ this.screen.cursorCol = cols - 1;
165
+ if (--this.screen.cursorRow < 0)
166
+ this.screen.cursorRow = rows - 1;
90
167
  }
91
168
  }
92
169
  else if (normalizedKey === 'ArrowRight') {
93
- // Stop at end of data (last non-space char + 1), not end of field
94
- const fieldStart = this.screen.offset(field.row, field.col);
95
- let lastData = field.col;
96
- for (let i = 0; i < field.length; i++) {
97
- if (this.screen.buffer[fieldStart + i] !== ' ')
98
- lastData = field.col + i + 1;
99
- }
100
- const rightLimit = Math.min(lastData, field.col + field.length - 1);
101
- if (this.screen.cursorCol < rightLimit) {
102
- this.screen.cursorCol++;
170
+ if (++this.screen.cursorCol >= cols) {
171
+ this.screen.cursorCol = 0;
172
+ if (++this.screen.cursorRow >= rows)
173
+ this.screen.cursorRow = 0;
103
174
  }
104
175
  }
176
+ else if (normalizedKey === 'ArrowUp') {
177
+ if (--this.screen.cursorRow < 0)
178
+ this.screen.cursorRow = rows - 1;
179
+ }
105
180
  else {
106
- // Up/Down: move to prev/next input field (reuse Tab/Backtab logic)
107
- return this.sendKey(normalizedKey === 'ArrowUp' ? 'Backtab' : 'Tab');
181
+ if (++this.screen.cursorRow >= rows)
182
+ this.screen.cursorRow = 0;
108
183
  }
109
184
  return true;
110
185
  }
@@ -154,51 +229,30 @@ export class TN5250Handler extends ProtocolHandler {
154
229
  }
155
230
  return true;
156
231
  }
157
- // Tab/Backtab: move cursor to next/previous input field.
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.
232
+ // Tab/Backtab: walk the non-bypass field list in spatial order
233
+ // (per lib5250 display.c:2089 — set_cursor_next_logical_field / prev).
234
+ // Resequence FCW 0x80xx overrides spatial order: fields with non-zero
235
+ // resequence come first in resequence order, non-resequenced fields
236
+ // follow in spatial order.
171
237
  //
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).
238
+ // NOTE: lib5250 does NOT filter fields by display attribute every
239
+ // non-bypass field is a valid tab target. Our parser's demotion pass
240
+ // already removes UIM artifact fields (wide NON_DISPLAY on non-sign-on
241
+ // screens) and decoration fields, so the `isInputField` check alone
242
+ // is sufficient here.
177
243
  if (normalizedKey === 'Tab' || normalizedKey === 'Backtab') {
178
- const allInputs = this.screen.fields.filter(f => this.screen.isInputField(f));
179
- if (allInputs.length === 0)
244
+ const inputFields = this.screen.fields.filter(f => this.screen.isInputField(f));
245
+ if (inputFields.length === 0)
180
246
  return false;
181
- // Determine ordering: resequence-aware if any field declares it.
182
- const hasResequence = allInputs.some(f => f.resequence && f.resequence > 0);
247
+ const hasResequence = inputFields.some(f => f.resequence && f.resequence > 0);
183
248
  const orderOf = (f) => {
184
249
  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
250
  const base = f.resequence && f.resequence > 0 ? f.resequence : 10000;
189
251
  return base * 1_000_000 + this.screen.offset(f.row, f.col);
190
252
  }
191
253
  return this.screen.offset(f.row, f.col);
192
254
  };
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));
198
- const inputFields = functional.length > 0 ? functional
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.
255
+ inputFields.sort((a, b) => orderOf(a) - orderOf(b));
202
256
  const cursorPos = this.screen.offset(this.screen.cursorRow, this.screen.cursorCol);
203
257
  const curIdx = inputFields.findIndex(f => {
204
258
  const start = this.screen.offset(f.row, f.col);
@@ -389,7 +443,9 @@ export class TN5250Handler extends ProtocolHandler {
389
443
  /**
390
444
  * Full auto-sign-in flow: wait for sign-in fields, fill credentials,
391
445
  * submit, wait for confirmation screen, and restore field values.
392
- * Returns the final screen data, or null if sign-in fields weren't found.
446
+ * Returns { screen, authenticated } `authenticated` is true only if
447
+ * the host accepted the credentials (no longer on the sign-on screen).
448
+ * Returns null if the sign-on fields weren't found at all.
393
449
  */
394
450
  async performAutoSignIn(username, password) {
395
451
  await this.waitForScreenWithFields(2, 5000);
@@ -398,7 +454,44 @@ export class TN5250Handler extends ProtocolHandler {
398
454
  return null;
399
455
  await this.waitForScreen(10000);
400
456
  this.restoreFields();
401
- return this.getScreenData();
457
+ let screen = this.getScreenData();
458
+ // Success check: the sign-on screen always has both the "Sign On"
459
+ // title row AND a NON_DISPLAY password input field. If either is
460
+ // missing, we've moved past it. A plain NON_DISPLAY field check alone
461
+ // would mis-flag UIM artifact fields (e.g. the 1-row NON_DISPLAY span
462
+ // at (1,2) on many post-sign-on screens).
463
+ const isSignOnScreen = (s) => (s.fields || []).some((f) => f.is_input && f.is_non_display)
464
+ && (s.content || '').includes('Sign On');
465
+ if (isSignOnScreen(screen)) {
466
+ return { screen, authenticated: false };
467
+ }
468
+ // Auto-dismiss post-sign-on confirmation screens ("Sign-on
469
+ // Information" / "Display Messages" / "Output queue waiting" / etc.)
470
+ // until we reach a screen that exposes a usable command line — an
471
+ // underscored input field wide enough for typical commands. This
472
+ // guarantees that a subsequent gracefulDestroy() can type SIGNOFF and
473
+ // actually terminate the interactive job, instead of leaving the
474
+ // device hanging until QDEVRCYACN reaps it (which on LMTDEVSSN=*YES
475
+ // user profiles counts against the quota → CPF1220 on next login).
476
+ const hasCommandLine = (s) => (s.fields || []).some((f) => f.is_input && !f.is_non_display && (f.length || 0) >= 20);
477
+ for (let attempt = 0; attempt < 4 && !hasCommandLine(screen); attempt++) {
478
+ const enterAid = this.encoder.buildAidResponse('Enter');
479
+ if (!enterAid)
480
+ break;
481
+ this.connection.sendRaw(enterAid);
482
+ await this.waitForScreen(5000);
483
+ const next = this.getScreenData();
484
+ // Bail out if the screen didn't change — we're stuck (likely an
485
+ // error message waiting for Reset, or a prompt we don't know how to
486
+ // answer). Don't press Enter into an unknown state.
487
+ if ((next.content || '') === (screen.content || ''))
488
+ break;
489
+ screen = next;
490
+ if (isSignOnScreen(screen)) {
491
+ return { screen, authenticated: false };
492
+ }
493
+ }
494
+ return { screen, authenticated: true };
402
495
  }
403
496
  /** Wait for the next screenChange event (or timeout with current screen). */
404
497
  waitForScreen(timeoutMs) {
@@ -434,45 +527,21 @@ export class TN5250Handler extends ProtocolHandler {
434
527
  });
435
528
  }
436
529
  /**
437
- * Set cursor position (for click-to-position). Validates the target is
438
- * inside an input field; if not, finds the nearest input field.
439
- * Returns true if the cursor was repositioned.
530
+ * Set cursor position (for click-to-position). Moves the cursor freely
531
+ * to the requested cell, matching lib5250 dbuffer.c:485-497 behaviour
532
+ * no field snapping. Typing at a non-field cell is separately inhibited
533
+ * by the encoder. Returns true always (the click landed somewhere).
534
+ *
535
+ * This is required for list screens like WRKSPLF: the user must be able
536
+ * to click on any row's Opt column, even though only one SF input field
537
+ * covers the entire list area.
440
538
  */
441
539
  setCursor(row, col) {
442
- // Clamp to screen bounds
443
540
  row = Math.max(0, Math.min(row, this.screen.rows - 1));
444
541
  col = Math.max(0, Math.min(col, this.screen.cols - 1));
445
- // Check if target is directly in an input field
446
- const field = this.screen.getFieldAt(row, col);
447
- if (field && this.screen.isInputField(field)) {
448
- this.screen.cursorRow = row;
449
- this.screen.cursorCol = col;
450
- return true;
451
- }
452
- // Find nearest input field (prefer same row, then closest overall)
453
- const inputFields = this.screen.fields.filter(f => this.screen.isInputField(f) && this.screen.hasNativeUnderscore(f));
454
- if (inputFields.length === 0)
455
- return false;
456
- const targetPos = this.screen.offset(row, col);
457
- let bestField = null;
458
- let bestDist = Infinity;
459
- for (const f of inputFields) {
460
- const fStart = this.screen.offset(f.row, f.col);
461
- const fEnd = fStart + f.length - 1;
462
- // Distance: 0 if inside, else distance to nearest edge
463
- const dist = targetPos < fStart ? fStart - targetPos
464
- : targetPos > fEnd ? targetPos - fEnd : 0;
465
- if (dist < bestDist) {
466
- bestDist = dist;
467
- bestField = f;
468
- }
469
- }
470
- if (bestField) {
471
- this.screen.cursorRow = bestField.row;
472
- this.screen.cursorCol = bestField.col;
473
- return true;
474
- }
475
- return false;
542
+ this.screen.cursorRow = row;
543
+ this.screen.cursorCol = col;
544
+ return true;
476
545
  }
477
546
  sendRaw(data) {
478
547
  this.connection.sendRaw(data);
@@ -482,6 +551,7 @@ export class TN5250Handler extends ProtocolHandler {
482
551
  this.removeAllListeners();
483
552
  }
484
553
  onRecord(record) {
554
+ const klBefore = this.screen.keyboardLocked;
485
555
  const modified = this.parser.parseRecord(record);
486
556
  // Send query reply if host sent a 5250 Query WSF
487
557
  if (this.parser.pendingQueryReply) {
@@ -498,8 +568,16 @@ export class TN5250Handler extends ProtocolHandler {
498
568
  if (this.screen.screenStack.length > 0 && this.parser.winRowOff === 0 && this.parser.winColOff === 0) {
499
569
  this.screen.synthesizeWindow();
500
570
  }
571
+ if (process.env.GS_DIAG_KL === '1') {
572
+ const cmdBytes = Array.from(record.slice(0, Math.min(12, record.length)))
573
+ .map((b) => b.toString(16).padStart(2, '0')).join(' ');
574
+ console.log(`[KL] kl: ${klBefore} -> ${this.screen.keyboardLocked} recLen=${record.length} head=${cmdBytes}`);
575
+ }
501
576
  this.emit('screenChange', this.screen.toScreenData());
502
577
  }
578
+ else if (process.env.GS_DIAG_KL === '1') {
579
+ console.log(`[KL] record NOT modified recLen=${record.length} kl=${this.screen.keyboardLocked}`);
580
+ }
503
581
  }
504
582
  }
505
583
  //# sourceMappingURL=tn5250-handler.js.map