password.js 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. /**
  2. * `password` type prompt
  3. */
  4. import chalk from 'chalk';
  5. import { map, takeUntil } from 'rxjs';
  6. import Base from './base.js';
  7. import observe from '../utils/events.js';
  8. function mask(input, maskChar) {
  9. input = String(input);
  10. maskChar = typeof maskChar === 'string' ? maskChar : '*';
  11. if (input.length === 0) {
  12. return '';
  13. }
  14. return new Array(input.length + 1).join(maskChar);
  15. }
  16. export default class PasswordPrompt extends Base {
  17. /**
  18. * Start the Inquiry session
  19. * @param {Function} cb Callback when prompt is done
  20. * @return {this}
  21. */
  22. _run(cb) {
  23. this.done = cb;
  24. const events = observe(this.rl);
  25. // Once user confirm (enter key)
  26. const submit = events.line.pipe(map(this.filterInput.bind(this)));
  27. const validation = this.handleSubmitEvents(submit);
  28. validation.success.forEach(this.onEnd.bind(this));
  29. validation.error.forEach(this.onError.bind(this));
  30. events.keypress
  31. .pipe(takeUntil(validation.success))
  32. .forEach(this.onKeypress.bind(this));
  33. // Init
  34. this.render();
  35. return this;
  36. }
  37. /**
  38. * Render the prompt to screen
  39. * @return {PasswordPrompt} self
  40. */
  41. render(error) {
  42. let message = this.getQuestion();
  43. let bottomContent = '';
  44. if (this.status === 'answered') {
  45. message += this.getMaskedValue(this.answer);
  46. } else {
  47. message += this.getMaskedValue(this.rl.line || '');
  48. }
  49. if (error) {
  50. bottomContent = '\n' + chalk.red('>> ') + error;
  51. }
  52. this.screen.render(message, bottomContent);
  53. }
  54. getMaskedValue(value) {
  55. if (this.status === 'answered') {
  56. return this.opt.mask
  57. ? chalk.cyan(mask(value, this.opt.mask))
  58. : chalk.italic.dim('[hidden]');
  59. }
  60. return this.opt.mask
  61. ? mask(value, this.opt.mask)
  62. : chalk.italic.dim('[input is hidden] ');
  63. }
  64. /**
  65. * Mask value during async filter/validation.
  66. */
  67. getSpinningValue(value) {
  68. return this.getMaskedValue(value);
  69. }
  70. /**
  71. * When user press `enter` key
  72. */
  73. filterInput(input) {
  74. if (!input) {
  75. return this.opt.default == null ? '' : this.opt.default;
  76. }
  77. return input;
  78. }
  79. onEnd(state) {
  80. this.status = 'answered';
  81. this.answer = state.value;
  82. // Re-render prompt
  83. this.render();
  84. this.screen.done();
  85. this.done(state.value);
  86. }
  87. onError(state) {
  88. this.render(state.isValid);
  89. }
  90. onKeypress() {
  91. // If user press a key, just clear the default value
  92. if (this.opt.default) {
  93. this.opt.default = undefined;
  94. }
  95. this.render();
  96. }
  97. }