All files / packages/tao-utils/src Transponder.js

4.76% Statements 2/42
0% Branches 0/30
0% Functions 0/13
4.76% Lines 2/42
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168      1x   1x                                                                                                                                                                                                                                                                                                                                    
import { Network } from '@tao.js/core';
 
// for backwards compatibility
const MAX_SAFE_INTEGER = Math.pow(2, 53) - 1;
 
let transponderId = 0;
function newTransponderId() {
  return (transponderId = ++transponderId % MAX_SAFE_INTEGER);
}
 
function transponderControl(transponderId, signal) {
  return { transponderId, signal };
}
 
/**
 * Allows use of Promises with a Network by returning a Promise from a signalling method
 * that will only resolve based on one of the handlers attached to the wrapped Network
 * being called.
 *
 * It is not recommended to attach a Transponder to your primary Network or Kernel as this will
 * resolve Promises with the first AppCon handled (unless that is the desired behavior).
 *
 * It is recommended to attach a Transponder to one of the other `utils` classes that filter
 * or have a subset of the handlers of your primary Network like a Channel.
 *
 * @export
 * @class Transponder
 */
export default class Transponder {
  /**
   *Creates an instance of Transponder.
   * @param {Network} network - the `Network` to wrap with a `Transponder`
   * @param {(string|function)} [id] - pass either a desired Channel ID value as a `string` or a `function` that will be used to generate a Channel ID
   *        the `function` will be called with a new Channel ID integer value to help ensure uniqueness
   * @param {number} [timeoutMs=0] - a timeout to be used when awaiting `Promises`
   *        - `0` - no timeout will be used
   *        - `> 0` - timeout will be used to `reject` the `Promise` if it is not signaled in time
   *        - use this to prevent unexpected behaviors
   * @param {Thenable} [promise=Promise] - a `Promise` constructor to be used when creating promises
   *        by a signalling method.
   * @param {boolean} [debug=false] - pass true to console.log internal activity
   * @memberof Transponder
   */
  constructor(network, id, timeoutMs = 0, promise = Promise, debug = false) {
    this._debug = debug;
    this._transponderId =
      typeof id === 'function'
        ? id(newTransponderId())
        : id || newTransponderId();
    const inTO = +timeoutMs || 0;
    this._timeoutMs = inTO > 0 ? inTO : 0;
    this._network =
      typeof network.use === 'function' ? network : network._network;
    this._network.use(this.handleSignalAppCon);
    this._promise = promise;
    this._cloneWithId = typeof id === 'function' ? id : undefined;
  }
 
  clone(cloneId) {
    const clone = new Transponder(
      this._network,
      cloneId || this._cloneWithId,
      this._timeoutMs,
      this._promise,
      this._debug
    );
    return clone;
  }
 
  /**
   * Attaches the `Transponder` to the `Network` it wraps by enabling the signalling to any
   * `Promises` that are created using one of the signalling methods.
   *
   * This is the inverse operation of the {@linkcode detach()} method and used to undo that
   * operation.
   *
   * `attach()` is unnecessary unless {@linkcode detach()} has been called as a newly
   * constructed `Transponder` is always attached to the `Network` it wraps.
   *
   * @see {@link detach}
   *
   * @returns this
   * @memberof Transponder
   */
  attach() {
    this._network.use(this.handleSignalAppCon);
    return this;
  }
 
  /**
   * Detaches the `Transponder` from the `Network` it wraps by disabling the signaling to any
   * `Promises` that are created using one of the signalling methods.
   *
   * Detach is temporary and can be reestablished using the {@link attach()} method.
   *
   * @see {@link attach()}
   *
   * @returns this
   * @memberof Transponder
   */
  detach() {
    this._network.stop(this.handleSignalAppCon);
    return this;
  }
 
  setCtx({ t, term, a, action, o, orient }, data) {
    const transponderId = this._transponderId;
    const timeoutMs = this._timeoutMs;
    const promise = this._promise;
 
    return new promise((resolve, reject) => {
      if (timeoutMs) {
        setTimeout(() => {
          reject(`reached timeout of: ${timeoutMs}ms`);
        }, timeoutMs);
      }
      const control = transponderControl(transponderId, resolve);
      this._network.setCtxControl(
        { t, term, a, action, o, orient },
        data,
        control
      );
    });
  }
 
  setAppCtx(ac) {
    const transponderId = this._transponderId;
    const timeoutMs = this._timeoutMs;
    const promise = this._promise;
 
    return new promise((resolve, reject) => {
      if (timeoutMs) {
        setTimeout(() => {
          reject(`reached timeout of: ${timeoutMs}ms`);
        }, timeoutMs);
      }
      const control = transponderControl(transponderId, resolve);
      this._network.setAppCtxControl(ac, control);
    });
  }
 
  handleSignalAppCon = (handler, ac, forwardAppCtx, control) => {
    this._debug &&
      console.log(
        `transponder{${this._transponderId}}::handleSignalFirstAppCon::ac:`,
        ac.unwrapCtx()
      );
    this._debug &&
      console.log(
        `transponder{${
          this._transponderId
        }}::handleSignalFirstAppCon::control:`,
        control
      );
    // first matching handler will signal the listener
    if (
      control.transponderId === this._transponderId &&
      control.signal &&
      !control.signalled
    ) {
      control.signalled = true;
      control.signal(ac);
    }
    // ALERT: handler will have already handled the AppCon before now
    // return handler.handleAppCon(ac, forwardAppCtx, control);
  };
}