text: import { Duplex } from 'readable-stream';
  import { randomId } from '@toruslabs/openlogin-utils';
  import { EventEmitter } from 'events';
  import stringify from 'fast-safe-stringify';
  import eos from 'end-of-stream';
  import once from 'once';
  import pump from 'pump';
  
  function noop() {
      return undefined;
  }
  const SYN = "SYN";
  const ACK = "ACK";
  const BRK = "BRK";
  class BasePostMessageStream extends Duplex {
      constructor({ name, target, targetWindow }) {
          super({
              objectMode: true,
          });
          this._init = false;
          this._haveSyn = false;
          this._name = name;
          this._target = target; // target origin
          this._targetWindow = targetWindow || window;
          this._origin = targetWindow ? "*" : window.location.origin;
          this._onMessage = this.onMessage.bind(this);
          this._synIntervalId = null;
          window.addEventListener("message", this._onMessage, false);
          this._handShake();
      }
      _break() {
          this.cork();
          this._write(BRK, null, noop);
          this._haveSyn = false;
          this._init = false;
      }
      _handShake() {
          this._write(SYN, null, noop);
          this.cork();
      }
      _onData(data) {
          if (!this._init) {
              // listen for handshake
              if (data === SYN) {
                  this._haveSyn = true;
                  this._write(ACK, null, noop);
              }
              else if (data === ACK) {
                  this._init = true;
                  if (!this._haveSyn) {
                      this._write(ACK, null, noop);
                  }
                  this.uncork();
              }
          }
          else if (data === BRK) {
              this._break();
          }
          else {
              // forward message
              try {
                  this.push(data);
              }
              catch (err) {
                  this.emit("error", err);
              }
          }
      }
      _postMessage(data) {
          const originConstraint = this._origin;
          this._targetWindow.postMessage({
              target: this._target,
              data,
          }, originConstraint);
      }
      onMessage(event) {
          const message = event.data;
          // validate message
          if ((this._origin !== "*" && event.origin !== this._origin) ||
              event.source !== this._targetWindow ||
              typeof message !== "object" ||
              message.target !== this._name ||
              !message.data) {
              return;
          }
          this._onData(message.data);
      }
      // eslint-disable-next-line class-methods-use-this
      _read() {
          return undefined;
      }
      _write(data, _, cb) {
          this._postMessage(data);
          cb();
      }
  }
  
  /*! *****************************************************************************
  Copyright (c) Microsoft Corporation.
  
  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted.
  
  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
  REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
  OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
  PERFORMANCE OF THIS SOFTWARE.
  ***************************************************************************** */
  
  function __awaiter(thisArg, _arguments, P, generator) {
      function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
      return new (P || (P = Promise))(function (resolve, reject) {
          function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
          function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
          function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
          step((generator = generator.apply(thisArg, _arguments || [])).next());
      });
  }
  
  function safeApply(handler, context, args) {
      try {
          Reflect.apply(handler, context, args);
      }
      catch (err) {
          // Throw error after timeout so as not to interrupt the stack
          setTimeout(() => {
              throw err;
          });
      }
  }
  function arrayClone(arr) {
      const n = arr.length;
      const copy = new Array(n);
      for (let i = 0; i < n; i += 1) {
          copy[i] = arr[i];
      }
      return copy;
  }
  class SafeEventEmitter extends EventEmitter {
      emit(type, ...args) {
          let doError = type === "error";
          const events = this._events;
          if (events !== undefined) {
              doError = doError && events.error === undefined;
          }
          else if (!doError) {
              return false;
          }
          // If there is no 'error' event listener then throw.
          if (doError) {
              let er;
              if (args.length > 0) {
                  [er] = args;
              }
              if (er instanceof Error) {
                  // Note: The comments on the `throw` lines are intentional, they show
                  // up in Node's output if this results in an unhandled exception.
                  throw er; // Unhandled 'error' event
              }
              // At least give some kind of context to the user
              const err = new Error(`Unhandled error.${er ? ` (${er.message})` : ""}`);
              err.context = er;
              throw err; // Unhandled 'error' event
          }
          const handler = events[type];
          if (handler === undefined) {
              return false;
          }
          if (typeof handler === "function") {
              safeApply(handler, this, args);
          }
          else {
              const len = handler.length;
              const listeners = arrayClone(handler);
              for (let i = 0; i < len; i += 1) {
                  safeApply(listeners[i], this, args);
              }
          }
          return true;
      }
  }
  
  class SerializableError extends Error {
      constructor({ code, message, data }) {
          if (!Number.isInteger(code)) {
              throw new Error("code must be an integer");
          }
          if (!message || typeof message !== "string") {
              throw new Error("message must be string");
          }
          super(message);
          this.code = code;
          if (data !== undefined) {
              this.data = data;
          }
      }
      toString() {
          return stringify({
              code: this.code,
              message: this.message,
              data: this.data,
              stack: this.stack,
          });
      }
  }
  
  function serializeError(error) {
      return error.toString();
  }
  const getRpcPromiseCallback = (resolve, reject, unwrapResult = true) => (error, response) => {
      if (error || response.error) {
          reject(error || response.error);
      }
      else if (!unwrapResult || Array.isArray(response)) {
          resolve(response);
      }
      else {
          resolve(response.result);
      }
  };
  function createErrorMiddleware(log) {
      return (req, res, next, end) => {
          try {
              // json-rpc-engine will terminate the request when it notices this error
              if (typeof req.method !== "string" || !req.method) {
                  res.error = new SerializableError({ code: -32603, message: "invalid method" });
                  end();
                  return;
              }
              next((done) => {
                  const { error } = res;
                  if (!error) {
                      return done();
                  }
                  log.error(`OpenLogin - RPC Error: ${error.message}`, error);
                  return done();
              });
          }
          catch (error) {
              log.error(`OpenLogin - RPC Error thrown: ${error.message}`, error);
              res.error = new SerializableError({ code: -32603, message: error.message });
              end();
          }
      };
  }
  function createStreamMiddleware() {
      const idMap = {};
      function readNoop() {
          return false;
      }
      const events = new SafeEventEmitter();
      function processResponse(res) {
          const context = idMap[res.id];
          if (!context) {
              throw new Error(`StreamMiddleware - Unknown response id "${res.id}"`);
          }
          delete idMap[res.id];
          // copy whole res onto original res
          Object.assign(context.res, res);
          // run callback on empty stack,
          // prevent internal stream-handler from catching errors
          setTimeout(context.end);
      }
      function processNotification(res) {
          events.emit("notification", res);
      }
      function processMessage(res, _encoding, cb) {
          let err;
          try {
              const isNotification = !res.id;
              if (isNotification) {
                  processNotification(res);
              }
              else {
                  processResponse(res);
              }
          }
          catch (_err) {
              err = _err;
          }
          // continue processing stream
          cb(err);
      }
      const stream = new Duplex({
          objectMode: true,
          read: readNoop,
          write: processMessage,
      });
      const middleware = (req, res, next, end) => {
          // write req to stream
          stream.push(req);
          // register request on id map
          idMap[req.id] = { req, res, next, end };
      };
      return { events, middleware, stream };
  }
  function createScaffoldMiddleware(handlers) {
      return (req, res, next, end) => {
          const handler = handlers[req.method];
          // if no handler, return
          if (handler === undefined) {
              return next();
          }
          // if handler is fn, call as middleware
          if (typeof handler === "function") {
              return handler(req, res, next, end);
          }
          // if handler is some other value, use as result
          res.result = handler;
          return end();
      };
  }
  function createIdRemapMiddleware() {
      return (req, res, next, _end) => {
          const originalId = req.id;
          const newId = randomId();
          req.id = newId;
          res.id = newId;
          next((done) => {
              req.id = originalId;
              res.id = originalId;
              done();
          });
      };
  }
  function createLoggerMiddleware(logger) {
      return (req, res, next, _) => {
          logger.debug("REQ", req, "RES", res);
          next();
      };
  }
  function createAsyncMiddleware(asyncMiddleware) {
      return (req, res, next, end) => __awaiter(this, void 0, void 0, function* () {
          // nextPromise is the key to the implementation
          // it is resolved by the return handler passed to the
          // "next" function
          let resolveNextPromise;
          const nextPromise = new Promise((resolve) => {
              resolveNextPromise = resolve;
          });
          let returnHandlerCallback = null;
          let nextWasCalled = false;
          // This will be called by the consumer's async middleware.
          const asyncNext = () => __awaiter(this, void 0, void 0, function* () {
              nextWasCalled = true;
              // We pass a return handler to next(). When it is called by the engine,
              // the consumer's async middleware will resume executing.
              // eslint-disable-next-line node/callback-return
              next((runReturnHandlersCallback) => {
                  // This callback comes from JRPCEngine._runReturnHandlers
                  returnHandlerCallback = runReturnHandlersCallback;
                  resolveNextPromise();
              });
              yield nextPromise;
          });
          try {
              yield asyncMiddleware(req, res, asyncNext);
              if (nextWasCalled) {
                  yield nextPromise; // we must wait until the return handler is called
                  returnHandlerCallback(null);
              }
              else {
                  end(null);
              }
          }
          catch (error) {
              if (returnHandlerCallback) {
                  returnHandlerCallback(error);
              }
              else {
                  end(error);
              }
          }
      });
  }
  
  /**
   * A JSON-RPC request and response processor.
   * Give it a stack of middleware, pass it requests, and get back responses.
   */
  class JRPCEngine extends SafeEventEmitter {
      constructor() {
          super();
          this._middleware = [];
      }
      /**
       * Add a middleware function to the engine's middleware stack.
       *
       * @param middleware - The middleware function to add.
       */
      push(middleware) {
          this._middleware.push(middleware);
      }
      handle(req, cb) {
          if (cb && typeof cb !== "function") {
              throw new Error('"callback" must be a function if provided.');
          }
          if (Array.isArray(req)) {
              if (cb) {
                  return this._handleBatch(req, cb);
              }
              return this._handleBatch(req);
          }
          if (cb) {
              return this._handle(req, cb);
          }
          return this._promiseHandle(req);
      }
      /**
       * Returns this engine as a middleware function that can be pushed to other
       * engines.
       *
       * @returns This engine as a middleware function.
       */
      asMiddleware() {
          return (req, res, next, end) => __awaiter(this, void 0, void 0, function* () {
              try {
                  const [middlewareError, isComplete, returnHandlers] = yield JRPCEngine._runAllMiddleware(req, res, this._middleware);
                  if (isComplete) {
                      yield JRPCEngine._runReturnHandlers(returnHandlers);
                      return end(middlewareError);
                  }
                  return next((handlerCallback) => __awaiter(this, void 0, void 0, function* () {
                      try {
                          yield JRPCEngine._runReturnHandlers(returnHandlers);
                      }
                      catch (error) {
                          return handlerCallback(error);
                      }
                      return handlerCallback();
                  }));
              }
              catch (error) {
                  return end(error);
              }
          });
      }
      _handleBatch(reqs, cb) {
          return __awaiter(this, void 0, void 0, function* () {
              // The order here is important
              try {
                  // 2. Wait for all requests to finish, or throw on some kind of fatal
                  // error
                  const responses = yield Promise.all(
                  // 1. Begin executing each request in the order received
                  reqs.map(this._promiseHandle.bind(this)));
                  // 3. Return batch response
                  if (cb) {
                      return cb(null, responses);
                  }
                  return responses;
              }
              catch (error) {
                  if (cb) {
                      return cb(error);
                  }
                  throw error;
              }
          });
      }
      /**
       * A promise-wrapped _handle.
       */
      _promiseHandle(req) {
          return new Promise((resolve) => {
              this._handle(req, (_err, res) => {
                  // There will always be a response, and it will always have any error
                  // that is caught and propagated.
                  resolve(res);
              });
          });
      }
      /**
       * Ensures that the request object is valid, processes it, and passes any
       * error and the response object to the given callback.
       *
       * Does not reject.
       */
      _handle(callerReq, cb) {
          return __awaiter(this, void 0, void 0, function* () {
              if (!callerReq || Array.isArray(callerReq) || typeof callerReq !== "object") {
                  const error = new SerializableError({ code: -32603, message: "request must be plain object" });
                  return cb(error, { id: undefined, jsonrpc: "2.0", error });
              }
              if (typeof callerReq.method !== "string") {
                  const error = new SerializableError({ code: -32603, message: "method must be string" });
                  return cb(error, { id: callerReq.id, jsonrpc: "2.0", error });
              }
              const req = Object.assign({}, callerReq);
              const res = {
                  id: req.id,
                  jsonrpc: req.jsonrpc,
              };
              let error = null;
              try {
                  yield this._processRequest(req, res);
              }
              catch (_error) {
                  // A request handler error, a re-thrown middleware error, or something
                  // unexpected.
                  error = _error;
              }
              if (error) {
                  // Ensure no result is present on an errored response
                  delete res.result;
                  if (!res.error) {
                      res.error = serializeError(error);
                  }
              }
              return cb(error, res);
          });
      }
      /**
       * For the given request and response, runs all middleware and their return
       * handlers, if any, and ensures that internal request processing semantics
       * are satisfied.
       */
      _processRequest(req, res) {
          return __awaiter(this, void 0, void 0, function* () {
              const [error, isComplete, returnHandlers] = yield JRPCEngine._runAllMiddleware(req, res, this._middleware);
              // Throw if "end" was not called, or if the response has neither a result
              // nor an error.
              JRPCEngine._checkForCompletion(req, res, isComplete);
              // The return handlers should run even if an error was encountered during
              // middleware processing.
              yield JRPCEngine._runReturnHandlers(returnHandlers);
              // Now we re-throw the middleware processing error, if any, to catch it
              // further up the call chain.
              if (error) {
                  throw error;
              }
          });
      }
      /**
       * Serially executes the given stack of middleware.
       *
       * @returns An array of any error encountered during middleware execution,
       * a boolean indicating whether the request was completed, and an array of
       * middleware-defined return handlers.
       */
      static _runAllMiddleware(req, res, middlewareStack) {
          return __awaiter(this, void 0, void 0, function* () {
              const returnHandlers = [];
              let error = null;
              let isComplete = false;
              // Go down stack of middleware, call and collect optional returnHandlers
              for (const middleware of middlewareStack) {
                  // eslint-disable-next-line no-await-in-loop
                  [error, isComplete] = yield JRPCEngine._runMiddleware(req, res, middleware, returnHandlers);
                  if (isComplete) {
                      break;
                  }
              }
              return [error, isComplete, returnHandlers.reverse()];
          });
      }
      /**
       * Runs an individual middleware.
       *
       * @returns An array of any error encountered during middleware exection,
       * and a boolean indicating whether the request should end.
       */
      static _runMiddleware(req, res, middleware, returnHandlers) {
          return new Promise((resolve) => {
              const end = (err) => {
                  const error = err || res.error;
                  if (error) {
                      res.error = serializeError(error);
                  }
                  // True indicates that the request should end
                  resolve([error, true]);
              };
              const next = (returnHandler) => {
                  if (res.error) {
                      end(res.error);
                  }
                  else {
                      if (returnHandler) {
                          if (typeof returnHandler !== "function") {
                              end(new SerializableError({ code: -32603, message: "JRPCEngine: 'next' return handlers must be functions" }));
                          }
                          returnHandlers.push(returnHandler);
                      }
                      // False indicates that the request should not end
                      resolve([null, false]);
                  }
              };
              try {
                  middleware(req, res, next, end);
              }
              catch (error) {
                  end(error);
              }
          });
      }
      /**
       * Serially executes array of return handlers. The request and response are
       * assumed to be in their scope.
       */
      static _runReturnHandlers(handlers) {
          return __awaiter(this, void 0, void 0, function* () {
              for (const handler of handlers) {
                  // eslint-disable-next-line no-await-in-loop
                  yield new Promise((resolve, reject) => {
                      handler((err) => (err ? reject(err) : resolve()));
                  });
              }
          });
      }
      /**
       * Throws an error if the response has neither a result nor an error, or if
       * the "isComplete" flag is falsy.
       */
      static _checkForCompletion(req, res, isComplete) {
          if (!("result" in res) && !("error" in res)) {
              throw new SerializableError({ code: -32603, message: "Response has no error or result for request" });
          }
          if (!isComplete) {
              throw new SerializableError({ code: -32603, message: "Nothing ended request" });
          }
      }
  }
  function mergeMiddleware(middlewareStack) {
      const engine = new JRPCEngine();
      middlewareStack.forEach((middleware) => engine.push(middleware));
      return engine.asMiddleware();
  }
  function createEngineStream(opts) {
      if (!opts || !opts.engine) {
          throw new Error("Missing engine parameter!");
      }
      const { engine } = opts;
      // eslint-disable-next-line prefer-const
      let stream;
      function read() {
          return undefined;
      }
      function write(req, _encoding, cb) {
          engine.handle(req, (_err, res) => {
              stream.push(res);
          });
          cb();
      }
      stream = new Duplex({ objectMode: true, read, write });
      // forward notifications
      if (engine.on) {
          engine.on("notification", (message) => {
              stream.push(message);
          });
      }
      return stream;
  }
  
  class Substream extends Duplex {
      constructor({ parent, name }) {
          super({ objectMode: true });
          this._parent = parent;
          this._name = name;
      }
      /**
       * Explicitly sets read operations to a no-op.
       */
      // eslint-disable-next-line class-methods-use-this
      _read() {
          return undefined;
      }
      /**
       * Called when data should be written to this writable stream.
       *
       * @param chunk - Arbitrary object to write
       * @param encoding - Encoding to use when writing payload
       * @param callback - Called when writing is complete or an error occurs
       */
      _write(chunk, _encoding, callback) {
          this._parent.push({
              name: this._name,
              data: chunk,
          });
          callback();
      }
  }
  
  const IGNORE_SUBSTREAM = Symbol("IGNORE_SUBSTREAM");
  class ObjectMultiplex extends Duplex {
      constructor(opts = {}) {
          super(Object.assign(Object.assign({}, opts), { objectMode: true }));
          this._substreams = {};
      }
      createStream(name) {
          // validate name
          if (!name) {
              throw new Error("ObjectMultiplex - name must not be empty");
          }
          if (this._substreams[name]) {
              throw new Error(`ObjectMultiplex - Substream for name "${name}" already exists`);
          }
          // create substream
          const substream = new Substream({ parent: this, name });
          this._substreams[name] = substream;
          // listen for parent stream to end
          // eslint-disable-next-line @typescript-eslint/no-use-before-define
          anyStreamEnd(this, (_error) => substream.destroy(_error || undefined));
          return substream;
      }
      // ignore streams (dont display orphaned data warning)
      ignoreStream(name) {
          // validate name
          if (!name) {
              throw new Error("ObjectMultiplex - name must not be empty");
          }
          if (this._substreams[name]) {
              throw new Error(`ObjectMultiplex - Substream for name "${name}" already exists`);
          }
          // set
          this._substreams[name] = IGNORE_SUBSTREAM;
      }
      // eslint-disable-next-line class-methods-use-this
      _read() {
          return undefined;
      }
      _write(chunk, _encoding, callback) {
          const { name, data } = chunk;
          if (!name) {
              window.console.warn(`ObjectMultiplex - malformed chunk without name "${chunk}"`);
              return callback();
          }
          // get corresponding substream
          const substream = this._substreams[name];
          if (!substream) {
              window.console.warn(`ObjectMultiplex - orphaned data for stream "${name}"`);
              return callback();
          }
          // push data into substream
          if (substream !== IGNORE_SUBSTREAM) {
              substream.push(data);
          }
          return callback();
      }
  }
  // util
  function anyStreamEnd(stream, _cb) {
      const cb = once(_cb);
      eos(stream, { readable: false }, cb);
      eos(stream, { writable: false }, cb);
  }
  function setupMultiplex(stream) {
      const mux = new ObjectMultiplex();
      mux.getStream = function streamHelper(name) {
          if (this._substreams[name]) {
              return this._substreams[name];
          }
          return this.createStream(name);
      };
      pump(stream, mux, stream, (err) => {
          if (err)
              window.console.error(err);
      });
      return mux;
  }
  
  class PostMessageStream extends BasePostMessageStream {
      _postMessage(data) {
          let originConstraint = this._origin;
          if (typeof data === "object") {
              const dataObj = data;
              if (typeof dataObj.data === "object") {
                  const dataObjData = dataObj.data;
                  if (Array.isArray(dataObjData.params) && dataObjData.params.length > 0) {
                      const dataObjDataParam = dataObjData.params[0];
                      if (dataObjDataParam._origin) {
                          originConstraint = dataObjDataParam._origin;
                      }
                      // add a constraint for the response
                      dataObjDataParam._origin = window.location.origin;
                  }
              }
          }
          this._targetWindow.postMessage({
              target: this._target,
              data,
          }, originConstraint);
      }
  }
  
  export { BasePostMessageStream, IGNORE_SUBSTREAM, JRPCEngine, ObjectMultiplex, PostMessageStream, SafeEventEmitter, SerializableError, Substream, createAsyncMiddleware, createEngineStream, createErrorMiddleware, createIdRemapMiddleware, createLoggerMiddleware, createScaffoldMiddleware, createStreamMiddleware, getRpcPromiseCallback, mergeMiddleware, serializeError, setupMultiplex };
  //# sourceMappingURL=openlogin-jrpc.esm.js.map
  