index.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. import { EventEmitter } from 'events';
  2. /**
  3. * Wrapper around the Cloudflare built-in socket that can be used by the `Connection`.
  4. */
  5. export class CloudflareSocket extends EventEmitter {
  6. constructor(ssl) {
  7. super();
  8. this.ssl = ssl;
  9. this.writable = false;
  10. this.destroyed = false;
  11. this._upgrading = false;
  12. this._upgraded = false;
  13. this._cfSocket = null;
  14. this._cfWriter = null;
  15. this._cfReader = null;
  16. }
  17. setNoDelay() {
  18. return this;
  19. }
  20. setKeepAlive() {
  21. return this;
  22. }
  23. ref() {
  24. return this;
  25. }
  26. unref() {
  27. return this;
  28. }
  29. async connect(port, host, connectListener) {
  30. try {
  31. log('connecting');
  32. if (connectListener)
  33. this.once('connect', connectListener);
  34. const options = this.ssl ? { secureTransport: 'starttls' } : {};
  35. const { connect } = await import('cloudflare:sockets');
  36. this._cfSocket = connect(`${host}:${port}`, options);
  37. this._cfWriter = this._cfSocket.writable.getWriter();
  38. this._addClosedHandler();
  39. this._cfReader = this._cfSocket.readable.getReader();
  40. if (this.ssl) {
  41. this._listenOnce().catch((e) => this.emit('error', e));
  42. }
  43. else {
  44. this._listen().catch((e) => this.emit('error', e));
  45. }
  46. await this._cfWriter.ready;
  47. log('socket ready');
  48. this.writable = true;
  49. this.emit('connect');
  50. return this;
  51. }
  52. catch (e) {
  53. this.emit('error', e);
  54. }
  55. }
  56. async _listen() {
  57. while (true) {
  58. log('awaiting receive from CF socket');
  59. const { done, value } = await this._cfReader.read();
  60. log('CF socket received:', done, value);
  61. if (done) {
  62. log('done');
  63. break;
  64. }
  65. this.emit('data', Buffer.from(value));
  66. }
  67. }
  68. async _listenOnce() {
  69. log('awaiting first receive from CF socket');
  70. const { done, value } = await this._cfReader.read();
  71. log('First CF socket received:', done, value);
  72. this.emit('data', Buffer.from(value));
  73. }
  74. write(data, encoding = 'utf8', callback = () => { }) {
  75. if (data.length === 0)
  76. return callback();
  77. if (typeof data === 'string')
  78. data = Buffer.from(data, encoding);
  79. log('sending data direct:', data);
  80. this._cfWriter.write(data).then(() => {
  81. log('data sent');
  82. callback();
  83. }, (err) => {
  84. log('send error', err);
  85. callback(err);
  86. });
  87. return true;
  88. }
  89. end(data = Buffer.alloc(0), encoding = 'utf8', callback = () => { }) {
  90. log('ending CF socket');
  91. this.write(data, encoding, (err) => {
  92. this._cfSocket.close();
  93. if (callback)
  94. callback(err);
  95. });
  96. return this;
  97. }
  98. destroy(reason) {
  99. log('destroying CF socket', reason);
  100. this.destroyed = true;
  101. return this.end();
  102. }
  103. startTls(options) {
  104. if (this._upgraded) {
  105. // Don't try to upgrade again.
  106. this.emit('error', 'Cannot call `startTls()` more than once on a socket');
  107. return;
  108. }
  109. this._cfWriter.releaseLock();
  110. this._cfReader.releaseLock();
  111. this._upgrading = true;
  112. this._cfSocket = this._cfSocket.startTls(options);
  113. this._cfWriter = this._cfSocket.writable.getWriter();
  114. this._cfReader = this._cfSocket.readable.getReader();
  115. this._addClosedHandler();
  116. this._listen().catch((e) => this.emit('error', e));
  117. }
  118. _addClosedHandler() {
  119. this._cfSocket.closed.then(() => {
  120. if (!this._upgrading) {
  121. log('CF socket closed');
  122. this._cfSocket = null;
  123. this.emit('close');
  124. }
  125. else {
  126. this._upgrading = false;
  127. this._upgraded = true;
  128. }
  129. }).catch((e) => this.emit('error', e));
  130. }
  131. }
  132. const debug = false;
  133. function dump(data) {
  134. if (data instanceof Uint8Array || data instanceof ArrayBuffer) {
  135. const hex = Buffer.from(data).toString('hex');
  136. const str = new TextDecoder().decode(data);
  137. return `\n>>> STR: "${str.replace(/\n/g, '\\n')}"\n>>> HEX: ${hex}\n`;
  138. }
  139. else {
  140. return data;
  141. }
  142. }
  143. function log(...args) {
  144. debug && console.log(...args.map(dump));
  145. }
  146. //# sourceMappingURL=index.js.map