1import * as Log from '../../log'; 2import { logCmdError } from '../../utils/errors'; 3 4const CTRL_C = '\u0003'; 5 6const debug = require('debug')('expo:start:interface:keyPressHandler') as typeof console.log; 7 8/** An abstract key stroke interceptor. */ 9export class KeyPressHandler { 10 private isInterceptingKeyStrokes = false; 11 private isHandlingKeyPress = false; 12 13 constructor(public onPress: (key: string) => Promise<any>) {} 14 15 /** Start observing interaction pause listeners. */ 16 createInteractionListener() { 17 // Support observing prompts. 18 let wasIntercepting = false; 19 20 const listener = ({ pause }: { pause: boolean }) => { 21 if (pause) { 22 // Track if we were already intercepting key strokes before pausing, so we can 23 // resume after pausing. 24 wasIntercepting = this.isInterceptingKeyStrokes; 25 this.stopInterceptingKeyStrokes(); 26 } else if (wasIntercepting) { 27 // Only start if we were previously intercepting. 28 this.startInterceptingKeyStrokes(); 29 } 30 }; 31 32 return listener; 33 } 34 35 private handleKeypress = async (key: string) => { 36 // Prevent sending another event until the previous event has finished. 37 if (this.isHandlingKeyPress && key !== CTRL_C) { 38 return; 39 } 40 this.isHandlingKeyPress = true; 41 try { 42 debug(`Key pressed: ${key}`); 43 await this.onPress(key); 44 } catch (error: any) { 45 await logCmdError(error); 46 } finally { 47 this.isHandlingKeyPress = false; 48 } 49 }; 50 51 /** Start intercepting all key strokes and passing them to the input `onPress` method. */ 52 startInterceptingKeyStrokes() { 53 if (this.isInterceptingKeyStrokes) { 54 return; 55 } 56 this.isInterceptingKeyStrokes = true; 57 const { stdin } = process; 58 // TODO: This might be here because of an old Node version. 59 if (!stdin.setRawMode) { 60 Log.warn('Using a non-interactive terminal, keyboard commands are disabled.'); 61 return; 62 } 63 stdin.setRawMode(true); 64 stdin.resume(); 65 stdin.setEncoding('utf8'); 66 stdin.on('data', this.handleKeypress); 67 } 68 69 /** Stop intercepting all key strokes. */ 70 stopInterceptingKeyStrokes() { 71 if (!this.isInterceptingKeyStrokes) { 72 return; 73 } 74 this.isInterceptingKeyStrokes = false; 75 const { stdin } = process; 76 stdin.removeListener('data', this.handleKeypress); 77 // TODO: This might be here because of an old Node version. 78 if (!stdin.setRawMode) { 79 Log.warn('Using a non-interactive terminal, keyboard commands are disabled.'); 80 return; 81 } 82 stdin.setRawMode(false); 83 stdin.resume(); 84 } 85} 86