redux-devtools icon indicating copy to clipboard operation
redux-devtools copied to clipboard

React-native Hermes app has an issue with `for await`

Open skurgansky-sugarcrm opened this issue 2 years ago • 21 comments

React-native bundler for hermes app doesn't support for await construction. I had to replace it in this file @redux-devtools/remote/lib/cjs/devTools.js at 4 places with this code.

let consumer = this.socket.listener('error').createConsumer();
while (true) {
          const {value: data, done} = await consumer.next();
          if (done) break;
        // for await (const data of this.socket.listener('error')) {

Then i got a connection up and running.

Could you fix that please ?

skurgansky-sugarcrm avatar Apr 02 '23 18:04 skurgansky-sugarcrm

Hey. I managed to connect with your suggested changes, but my state is still undefined and doesn't really look like connecting. Would you mind sharing some extra info on how you managed to solve the issue? Thanks.

LunatiqueCoder avatar Jul 01 '23 07:07 LunatiqueCoder

"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.composeWithDevTools = composeWithDevTools;
exports.default = void 0;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _jsan = require("jsan");
var _socketclusterClient = _interopRequireDefault(require("socketcluster-client"));
var _configureStore = _interopRequireDefault(require("./configureStore"));
var _constants = require("./constants");
var _rnHostDetect = _interopRequireDefault(require("rn-host-detect"));
var _utils = require("@redux-devtools/utils");
function async(fn) {
  setTimeout(fn, 0);
}
function str2array(str) {
  return typeof str === 'string' ? [str] : str && str.length > 0 ? str : undefined;
}
function getRandomId() {
  return Math.random().toString(36).substr(2);
}
class DevToolsEnhancer {
  constructor() {
    var _this = this;
    (0, _defineProperty2.default)(this, "errorCounts", {});
    (0, _defineProperty2.default)(this, "send", () => {
      if (!this.instanceId) this.instanceId = this.socket && this.socket.id || getRandomId();
      try {
        fetch(this.sendTo, {
          method: 'POST',
          headers: {
            'content-type': 'application/json'
          },
          body: JSON.stringify({
            type: 'STATE',
            id: this.instanceId,
            name: this.instanceName,
            payload: (0, _jsan.stringify)(this.getLiftedState())
          })
        }).catch(function (err) {
          console.log(err);
        });
      } catch (err) {
        console.log(err);
      }
    });
    (0, _defineProperty2.default)(this, "handleMessages", message => {
      if (message.type === 'IMPORT' || message.type === 'SYNC' &&
      // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
      this.socket.id &&
      // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
      message.id !== this.socket.id) {
        this.store.liftedStore.dispatch({
          type: 'IMPORT_STATE',
          // eslint-disable-next-line @typescript-eslint/ban-types
          nextLiftedState: (0, _jsan.parse)(message.state)
        });
      } else if (message.type === 'UPDATE') {
        this.relay('STATE', this.getLiftedState());
      } else if (message.type === 'START') {
        this.isMonitored = true;
        if (typeof this.actionCreators === 'function') this.actionCreators = this.actionCreators();
        this.relay('STATE', this.getLiftedState(), this.actionCreators);
      } else if (message.type === 'STOP' || message.type === 'DISCONNECTED') {
        this.isMonitored = false;
        this.relay('STOP');
      } else if (message.type === 'ACTION') {
        this.dispatchRemotely(message.action);
      } else if (message.type === 'DISPATCH') {
        this.store.liftedStore.dispatch(message.action);
      }
    });
    (0, _defineProperty2.default)(this, "sendError", errorAction => {
      // Prevent flooding
      if (errorAction.message && errorAction.message === this.lastErrorMsg) return;
      this.lastErrorMsg = errorAction.message;
      async(() => {
        this.store.dispatch(errorAction);
        if (!this.started) this.send();
      });
    });
    (0, _defineProperty2.default)(this, "stop", keepConnected => {
      this.started = false;
      this.isMonitored = false;
      if (!this.socket) return;
      void this.socket.unsubscribe(this.channel);
      this.socket.closeChannel(this.channel);
      if (!keepConnected) {
        this.socket.disconnect();
      }
    });
    (0, _defineProperty2.default)(this, "start", () => {
      if (this.started || this.socket && this.socket.getState() === this.socket.CONNECTING) return;
      this.socket = _socketclusterClient.default.create(this.socketOptions);
      void (async () => {
        let consumer = this.socket.listener('error').createConsumer();
        while (true) {
          const {value: data, done} = await consumer.next();
          if (done) break;
        // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
        // for await (const data of this.socket.listener('error')) {
          // if we've already had this error before, increment it's counter, otherwise assign it '1' since we've had the error once.
          // eslint-disable-next-line no-prototype-builtins,@typescript-eslint/no-unsafe-argument
          this.errorCounts[data.error.name] = this.errorCounts.hasOwnProperty(data.error.name) ? this.errorCounts[data.error.name] + 1 : 1;
          if (this.suppressConnectErrors) {
            if (this.errorCounts[data.error.name] === 1) {
              console.log('remote-redux-devtools: Socket connection errors are being suppressed. ' + '\n' + "This can be disabled by setting suppressConnectErrors to 'false'.");
              console.log(data.error);
            }
          } else {
            console.log(data.error);
          }
        }
      })();
      void (async () => {
        let consumer = this.socket.listener('connect').createConsumer();
        while (true) {
          const {value: data, done} = await consumer.next();
          if (done) break;
        // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
        // for await (const data of this.socket.listener('connect')) {
          console.log('connected to remotedev-server');
          this.errorCounts = {}; // clear the errorCounts object, so that we'll log any new errors in the event of a disconnect
          this.login();
        }
      })();
      void (async () => {
        let consumer = this.socket.listener('disconnect').createConsumer();
        while (true) {
          const {value: data, done} = await consumer.next();
          if (done) break;
        // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
        // for await (const data of this.socket.listener('disconnect')) {
          this.stop(true);
        }
      })();
    });
    (0, _defineProperty2.default)(this, "checkForReducerErrors", function () {
      let liftedState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _this.getLiftedStateRaw();
      if (liftedState.computedStates[liftedState.currentStateIndex].error) {
        if (_this.started) _this.relay('STATE', (0, _utils.filterStagedActions)(liftedState, _this.filters));else _this.send();
        return true;
      }
      return false;
    });
    (0, _defineProperty2.default)(this, "monitorReducer", function () {
      let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
      let action = arguments.length > 1 ? arguments[1] : undefined;
      _this.lastAction = action.type;
      if (!_this.started && _this.sendOnError === 2 && _this.store.liftedStore) async(_this.checkForReducerErrors);else if (action.action) {
        if (_this.startOn && !_this.started && _this.startOn.indexOf(action.action.type) !== -1) async(_this.start);else if (_this.stopOn && _this.started && _this.stopOn.indexOf(action.action.type) !== -1) async(_this.stop);else if (_this.sendOn && !_this.started && _this.sendOn.indexOf(action.action.type) !== -1) async(_this.send);
      }
      return state;
    });
    (0, _defineProperty2.default)(this, "enhance", function () {
      let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
      _this.init({
        ...options,
        hostname: (0, _rnHostDetect.default)(options.hostname || 'localhost')
      });
      const realtime = typeof options.realtime === 'undefined' ? process.env.NODE_ENV === 'development' : options.realtime;
      if (!realtime && !(_this.startOn || _this.sendOn || _this.sendOnError)) return f => f;
      const maxAge = options.maxAge || 30;
      return next => {
        return (reducer, initialState) => {
          _this.store = (0, _configureStore.default)(next, _this.monitorReducer, {
            maxAge,
            trace: options.trace,
            traceLimit: options.traceLimit,
            shouldCatchErrors: !!_this.sendOnError,
            shouldHotReload: options.shouldHotReload,
            shouldRecordChanges: options.shouldRecordChanges,
            shouldStartLocked: options.shouldStartLocked,
            pauseActionType: options.pauseActionType || '@@PAUSED'
          })(reducer, initialState);
          if (realtime) _this.start();
          _this.store.subscribe(() => {
            if (_this.isMonitored) _this.handleChange(_this.store.getState(), _this.getLiftedStateRaw(), maxAge);
          });
          return _this.store;
        };
      };
    });
  }
  getLiftedStateRaw() {
    return this.store.liftedStore.getState();
  }
  getLiftedState() {
    return (0, _utils.filterStagedActions)(this.getLiftedStateRaw(), this.filters);
  }
  relay(type, state, action, nextActionId) {
    const message = {
      type,
      // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
      id: this.socket.id,
      name: this.instanceName,
      instanceId: this.appInstanceId
    };
    if (state) {
      message.payload = type === 'ERROR' ? state : (0, _jsan.stringify)((0, _utils.filterState)(state, type, this.filters, this.stateSanitizer, this.actionSanitizer, nextActionId));
    }
    if (type === 'ACTION') {
      message.action = (0, _jsan.stringify)(!this.actionSanitizer ? action : this.actionSanitizer(action.action, nextActionId - 1));
      message.isExcess = this.isExcess;
      message.nextActionId = nextActionId;
    } else if (action) {
      message.action = action;
    }
    // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
    void this.socket.transmit(this.socket.id ? 'log' : 'log-noid', message);
  }
  dispatchRemotely(action) {
    try {
      const result = (0, _utils.evalAction)(action, this.actionCreators);
      this.store.dispatch(result);
    } catch (e) {
      this.relay('ERROR', e.message);
    }
  }
  init(options) {
    this.instanceName = options.name;
    this.appInstanceId = getRandomId();
    const {
      blacklist,
      whitelist,
      denylist,
      allowlist
    } = options.filters || {};
    this.filters = (0, _utils.getLocalFilter)({
      actionsDenylist: denylist ?? options.actionsDenylist ?? blacklist ?? options.actionsBlacklist,
      actionsAllowlist: allowlist ?? options.actionsAllowlist ?? whitelist ?? options.actionsWhitelist
    });
    if (options.port) {
      this.socketOptions = {
        port: options.port,
        hostname: options.hostname || 'localhost',
        secure: options.secure
      };
    } else this.socketOptions = _constants.defaultSocketOptions;
    this.suppressConnectErrors = options.suppressConnectErrors !== undefined ? options.suppressConnectErrors : true;
    this.startOn = str2array(options.startOn);
    this.stopOn = str2array(options.stopOn);
    this.sendOn = str2array(options.sendOn);
    this.sendOnError = options.sendOnError;
    if (this.sendOn || this.sendOnError) {
      this.sendTo = options.sendTo || `${this.socketOptions.secure ? 'https' : 'http'}://${this.socketOptions.hostname}:${this.socketOptions.port}`;
      this.instanceId = options.id;
    }
    if (this.sendOnError === 1) (0, _utils.catchErrors)(this.sendError);
    if (options.actionCreators) this.actionCreators = () => (0, _utils.getActionsArray)(options.actionCreators);
    this.stateSanitizer = options.stateSanitizer;
    this.actionSanitizer = options.actionSanitizer;
  }
  login() {
    void (async () => {
      try {
        // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
        const channelName = await this.socket.invoke('login', 'master');
        this.channel = channelName;
        let consumer = this.socket.subscribe(channelName).createConsumer();
        while (true) {
          const {value: data, done} = await consumer.next();
          if (done) break;
        // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
        // for await (const data of this.socket.subscribe(channelName)) {
          this.handleMessages(data);
        }
      } catch (error) {
        console.log(error);
      }
    })();
    this.started = true;
    this.relay('START');
  }
  // eslint-disable-next-line @typescript-eslint/ban-types
  handleChange(state, liftedState, maxAge) {
    if (this.checkForReducerErrors(liftedState)) return;
    if (this.lastAction === 'PERFORM_ACTION') {
      const nextActionId = liftedState.nextActionId;
      const liftedAction = liftedState.actionsById[nextActionId - 1];
      if ((0, _utils.isFiltered)(liftedAction.action, this.filters)) return;
      this.relay('ACTION', state, liftedAction, nextActionId);
      if (!this.isExcess && maxAge) this.isExcess = liftedState.stagedActionIds.length >= maxAge;
    } else {
      if (this.lastAction === 'JUMP_TO_STATE') return;
      if (this.lastAction === 'PAUSE_RECORDING') {
        this.paused = liftedState.isPaused;
      } else if (this.lastAction === 'LOCK_CHANGES') {
        this.locked = liftedState.isLocked;
      }
      if (this.paused || this.locked) {
        if (this.lastAction) this.lastAction = undefined;else return;
      }
      this.relay('STATE', (0, _utils.filterStagedActions)(liftedState, this.filters));
    }
  }
}
var _default = options => new DevToolsEnhancer().enhance(options);
exports.default = _default;
const compose = options => function () {
  for (var _len = arguments.length, funcs = new Array(_len), _key = 0; _key < _len; _key++) {
    funcs[_key] = arguments[_key];
  }
  return function () {
    const devToolsEnhancer = new DevToolsEnhancer();
    function preEnhancer(createStore) {
      return (reducer, preloadedState) => {
        devToolsEnhancer.store = createStore(reducer, preloadedState);
        return {
          ...devToolsEnhancer.store,
          dispatch: action => devToolsEnhancer.locked ? action : devToolsEnhancer.store.dispatch(action)
        };
      };
    }
    for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
      args[_key2] = arguments[_key2];
    }
    return [preEnhancer, ...funcs].reduceRight((composed, f) => f(composed), devToolsEnhancer.enhance(options)(...args));
  };
};
function composeWithDevTools() {
  for (var _len3 = arguments.length, funcs = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
    funcs[_key3] = arguments[_key3];
  }
  if (funcs.length === 0) {
    return new DevToolsEnhancer().enhance();
  }
  if (funcs.length === 1 && typeof funcs[0] === 'object') {
    return compose(funcs[0]);
  }
  return compose({})(...funcs);
}

skurgansky-sugarcrm avatar Jul 01 '23 16:07 skurgansky-sugarcrm

OMG IT WORKED THANK YOU SO MUCH DUDE 🎉

With patch-packge, you can add the file below 👇 in your patches folder with @redux-devtools+remote+0.8.0.patch as the filename

Patch file
diff --git a/node_modules/@redux-devtools/remote/lib/cjs/devTools.js b/node_modules/@redux-devtools/remote/lib/cjs/devTools.js
index 5e9a11f..1a48048 100644
--- a/node_modules/@redux-devtools/remote/lib/cjs/devTools.js
+++ b/node_modules/@redux-devtools/remote/lib/cjs/devTools.js
@@ -49,13 +49,13 @@ class DevToolsEnhancer {
     });
     (0, _defineProperty2.default)(this, "handleMessages", message => {
       if (message.type === 'IMPORT' || message.type === 'SYNC' &&
-      // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
-      this.socket.id &&
-      // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
-      message.id !== this.socket.id) {
+          // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
+          this.socket.id &&
+          // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
+          message.id !== this.socket.id) {
         this.store.liftedStore.dispatch({
           type: 'IMPORT_STATE',
-          // eslint-disable-next-line @typescript-eslint/ban-types
+// eslint-disable-next-line @typescript-eslint/ban-types
           nextLiftedState: (0, _jsan.parse)(message.state)
         });
       } else if (message.type === 'UPDATE') {
@@ -74,7 +74,7 @@ class DevToolsEnhancer {
       }
     });
     (0, _defineProperty2.default)(this, "sendError", errorAction => {
-      // Prevent flooding
+// Prevent flooding
       if (errorAction.message && errorAction.message === this.lastErrorMsg) return;
       this.lastErrorMsg = errorAction.message;
       async(() => {
@@ -96,10 +96,14 @@ class DevToolsEnhancer {
       if (this.started || this.socket && this.socket.getState() === this.socket.CONNECTING) return;
       this.socket = _socketclusterClient.default.create(this.socketOptions);
       void (async () => {
-        // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
-        for await (const data of this.socket.listener('error')) {
-          // if we've already had this error before, increment it's counter, otherwise assign it '1' since we've had the error once.
-          // eslint-disable-next-line no-prototype-builtins,@typescript-eslint/no-unsafe-argument
+        let consumer = this.socket.listener('error').createConsumer();
+        while (true) {
+          const {value: data, done} = await consumer.next();
+          if (done) break;
+// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
+// for await (const data of this.socket.listener('error')) {
+// if we've already had this error before, increment it's counter, otherwise assign it '1' since we've had the error once.
+// eslint-disable-next-line no-prototype-builtins,@typescript-eslint/no-unsafe-argument
           this.errorCounts[data.error.name] = this.errorCounts.hasOwnProperty(data.error.name) ? this.errorCounts[data.error.name] + 1 : 1;
           if (this.suppressConnectErrors) {
             if (this.errorCounts[data.error.name] === 1) {
@@ -112,16 +116,24 @@ class DevToolsEnhancer {
         }
       })();
       void (async () => {
-        // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
-        for await (const data of this.socket.listener('connect')) {
+        let consumer = this.socket.listener('connect').createConsumer();
+        while (true) {
+          const {value: data, done} = await consumer.next();
+          if (done) break;
+// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
+// for await (const data of this.socket.listener('connect')) {
           console.log('connected to remotedev-server');
           this.errorCounts = {}; // clear the errorCounts object, so that we'll log any new errors in the event of a disconnect
           this.login();
         }
       })();
       void (async () => {
-        // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
-        for await (const data of this.socket.listener('disconnect')) {
+        let consumer = this.socket.listener('disconnect').createConsumer();
+        while (true) {
+          const {value: data, done} = await consumer.next();
+          if (done) break;
+// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
+// for await (const data of this.socket.listener('disconnect')) {
           this.stop(true);
         }
       })();
@@ -162,7 +174,7 @@ class DevToolsEnhancer {
             shouldHotReload: options.shouldHotReload,
             shouldRecordChanges: options.shouldRecordChanges,
             shouldStartLocked: options.shouldStartLocked,
-            pauseActionType: options.pauseActionType || '@@PAUSED'
+            pauseActionType: options.pauseActionType || '@@Paused'
           })(reducer, initialState);
           if (realtime) _this.start();
           _this.store.subscribe(() => {
@@ -182,7 +194,7 @@ class DevToolsEnhancer {
   relay(type, state, action, nextActionId) {
     const message = {
       type,
-      // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
+// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
       id: this.socket.id,
       name: this.instanceName,
       instanceId: this.appInstanceId
@@ -197,7 +209,7 @@ class DevToolsEnhancer {
     } else if (action) {
       message.action = action;
     }
-    // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
+// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
     void this.socket.transmit(this.socket.id ? 'log' : 'log-noid', message);
   }
   dispatchRemotely(action) {
@@ -234,7 +246,11 @@ class DevToolsEnhancer {
     this.sendOn = str2array(options.sendOn);
     this.sendOnError = options.sendOnError;
     if (this.sendOn || this.sendOnError) {
-      this.sendTo = options.sendTo || `${this.socketOptions.secure ? 'https' : 'http'}://${this.socketOptions.hostname}:${this.socketOptions.port}`;
+      this.sendTo =
+          options.sendTo ||
+          `${this.socketOptions.secure ? 'https' : 'http'}://${
+              this.socketOptions.hostname
+          }:${this.socketOptions.port}`;
       this.instanceId = options.id;
     }
     if (this.sendOnError === 1) (0, _utils.catchErrors)(this.sendError);
@@ -245,11 +261,15 @@ class DevToolsEnhancer {
   login() {
     void (async () => {
       try {
-        // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
+// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
         const channelName = await this.socket.invoke('login', 'master');
         this.channel = channelName;
-        // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
-        for await (const data of this.socket.subscribe(channelName)) {
+        let consumer = this.socket.subscribe(channelName).createConsumer();
+        while (true) {
+          const {value: data, done} = await consumer.next();
+          if (done) break;
+// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
+// for await (const data of this.socket.subscribe(channelName)) {
           this.handleMessages(data);
         }
       } catch (error) {
@@ -259,7 +279,7 @@ class DevToolsEnhancer {
     this.started = true;
     this.relay('START');
   }
-  // eslint-disable-next-line @typescript-eslint/ban-types
+// eslint-disable-next-line @typescript-eslint/ban-types
   handleChange(state, liftedState, maxAge) {
     if (this.checkForReducerErrors(liftedState)) return;
     if (this.lastAction === 'PERFORM_ACTION') {


Your @redux-devtools/remote/lib/cjs/devTools.js should look like this in the end: 👇

Complete file
'use strict';

var _interopRequireDefault = require('@babel/runtime/helpers/interopRequireDefault');
Object.defineProperty(exports, '__esModule', {
  value: true,
});
exports.composeWithDevTools = composeWithDevTools;
exports.default = void 0;
var _defineProperty2 = _interopRequireDefault(
  require('@babel/runtime/helpers/defineProperty'),
);
var _jsan = require('jsan');
var _socketclusterClient = _interopRequireDefault(
  require('socketcluster-client'),
);
var _configureStore = _interopRequireDefault(require('./configureStore'));
var _constants = require('./constants');
var _rnHostDetect = _interopRequireDefault(require('rn-host-detect'));
var _utils = require('@redux-devtools/utils');
function async(fn) {
  setTimeout(fn, 0);
}
function str2array(str) {
  return typeof str === 'string'
    ? [str]
    : str && str.length > 0
    ? str
    : undefined;
}
function getRandomId() {
  return Math.random().toString(36).substr(2);
}
class DevToolsEnhancer {
  constructor() {
    var _this = this;
    (0, _defineProperty2.default)(this, 'errorCounts', {});
    (0, _defineProperty2.default)(this, 'send', () => {
      if (!this.instanceId) {
        this.instanceId = (this.socket && this.socket.id) || getRandomId();
      }
      try {
        fetch(this.sendTo, {
          method: 'POST',
          headers: {
            'content-type': 'application/json',
          },
          body: JSON.stringify({
            type: 'STATE',
            id: this.instanceId,
            name: this.instanceName,
            payload: (0, _jsan.stringify)(this.getLiftedState()),
          }),
        }).catch(function (err) {
          console.log(err);
        });
      } catch (err) {
        console.log(err);
      }
    });
    (0, _defineProperty2.default)(this, 'handleMessages', message => {
      if (
        message.type === 'IMPORT' ||
        (message.type === 'SYNC' &&
          this.socket.id &&
          message.id !== this.socket.id)
      ) {
        this.store.liftedStore.dispatch({
          type: 'IMPORT_STATE',

          nextLiftedState: (0, _jsan.parse)(message.state),
        });
      } else if (message.type === 'UPDATE') {
        this.relay('STATE', this.getLiftedState());
      } else if (message.type === 'START') {
        this.isMonitored = true;
        if (typeof this.actionCreators === 'function') {
          this.actionCreators = this.actionCreators();
        }
        this.relay('STATE', this.getLiftedState(), this.actionCreators);
      } else if (message.type === 'STOP' || message.type === 'DISCONNECTED') {
        this.isMonitored = false;
        this.relay('STOP');
      } else if (message.type === 'ACTION') {
        this.dispatchRemotely(message.action);
      } else if (message.type === 'DISPATCH') {
        this.store.liftedStore.dispatch(message.action);
      }
    });
    (0, _defineProperty2.default)(this, 'sendError', errorAction => {
      // Prevent flooding
      if (errorAction.message && errorAction.message === this.lastErrorMsg) {
        return;
      }
      this.lastErrorMsg = errorAction.message;
      async(() => {
        this.store.dispatch(errorAction);
        if (!this.started) {
          this.send();
        }
      });
    });
    (0, _defineProperty2.default)(this, 'stop', keepConnected => {
      this.started = false;
      this.isMonitored = false;
      if (!this.socket) {
        return;
      }
      void this.socket.unsubscribe(this.channel);
      this.socket.closeChannel(this.channel);
      if (!keepConnected) {
        this.socket.disconnect();
      }
    });
    (0, _defineProperty2.default)(this, 'start', () => {
      if (
        this.started ||
        (this.socket && this.socket.getState() === this.socket.CONNECTING)
      ) {
        return;
      }
      this.socket = _socketclusterClient.default.create(this.socketOptions);
      void (async () => {
        let consumer = this.socket.listener('error').createConsumer();
        while (true) {
          const {value: data, done} = await consumer.next();
          if (done) {
            break;
          }

          // for await (const data of this.socket.listener('error')) {
          // if we've already had this error before, increment it's counter, otherwise assign it '1' since we've had the error once.

          this.errorCounts[data.error.name] = this.errorCounts.hasOwnProperty(
            data.error.name,
          )
            ? this.errorCounts[data.error.name] + 1
            : 1;
          if (this.suppressConnectErrors) {
            if (this.errorCounts[data.error.name] === 1) {
              console.log(
                'remote-redux-devtools: Socket connection errors are being suppressed. ' +
                  '\n' +
                  "This can be disabled by setting suppressConnectErrors to 'false'.",
              );
              console.log(data.error);
            }
          } else {
            console.log(data.error);
          }
        }
      })();
      void (async () => {
        let consumer = this.socket.listener('connect').createConsumer();
        while (true) {
          const {value: data, done} = await consumer.next();
          if (done) {
            break;
          }

          // for await (const data of this.socket.listener('connect')) {
          console.log('connected to remotedev-server');
          this.errorCounts = {}; // clear the errorCounts object, so that we'll log any new errors in the event of a disconnect
          this.login();
        }
      })();
      void (async () => {
        let consumer = this.socket.listener('disconnect').createConsumer();
        while (true) {
          const {value: data, done} = await consumer.next();
          if (done) {
            break;
          }

          // for await (const data of this.socket.listener('disconnect')) {
          this.stop(true);
        }
      })();
    });
    (0, _defineProperty2.default)(this, 'checkForReducerErrors', function () {
      let liftedState =
        arguments.length > 0 && arguments[0] !== undefined
          ? arguments[0]
          : _this.getLiftedStateRaw();
      if (liftedState.computedStates[liftedState.currentStateIndex].error) {
        if (_this.started) {
          _this.relay(
            'STATE',
            (0, _utils.filterStagedActions)(liftedState, _this.filters),
          );
        } else {
          _this.send();
        }
        return true;
      }
      return false;
    });
    (0, _defineProperty2.default)(this, 'monitorReducer', function () {
      let state =
        arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
      let action = arguments.length > 1 ? arguments[1] : undefined;
      _this.lastAction = action.type;
      if (
        !_this.started &&
        _this.sendOnError === 2 &&
        _this.store.liftedStore
      ) {
        async(_this.checkForReducerErrors);
      } else if (action.action) {
        if (
          _this.startOn &&
          !_this.started &&
          _this.startOn.indexOf(action.action.type) !== -1
        ) {
          async(_this.start);
        } else if (
          _this.stopOn &&
          _this.started &&
          _this.stopOn.indexOf(action.action.type) !== -1
        ) {
          async(_this.stop);
        } else if (
          _this.sendOn &&
          !_this.started &&
          _this.sendOn.indexOf(action.action.type) !== -1
        ) {
          async(_this.send);
        }
      }
      return state;
    });
    (0, _defineProperty2.default)(this, 'enhance', function () {
      let options =
        arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
      _this.init({
        ...options,
        hostname: (0, _rnHostDetect.default)(options.hostname || 'localhost'),
      });
      const realtime =
        typeof options.realtime === 'undefined'
          ? process.env.NODE_ENV === 'development'
          : options.realtime;
      if (!realtime && !(_this.startOn || _this.sendOn || _this.sendOnError)) {
        return f => f;
      }
      const maxAge = options.maxAge || 30;
      return next => {
        return (reducer, initialState) => {
          _this.store = (0, _configureStore.default)(
            next,
            _this.monitorReducer,
            {
              maxAge,
              trace: options.trace,
              traceLimit: options.traceLimit,
              shouldCatchErrors: !!_this.sendOnError,
              shouldHotReload: options.shouldHotReload,
              shouldRecordChanges: options.shouldRecordChanges,
              shouldStartLocked: options.shouldStartLocked,
              pauseActionType: options.pauseActionType || '@@Paused',
            },
          )(reducer, initialState);
          if (realtime) {
            _this.start();
          }
          _this.store.subscribe(() => {
            if (_this.isMonitored) {
              _this.handleChange(
                _this.store.getState(),
                _this.getLiftedStateRaw(),
                maxAge,
              );
            }
          });
          return _this.store;
        };
      };
    });
  }
  getLiftedStateRaw() {
    return this.store.liftedStore.getState();
  }
  getLiftedState() {
    return (0, _utils.filterStagedActions)(
      this.getLiftedStateRaw(),
      this.filters,
    );
  }
  relay(type, state, action, nextActionId) {
    const message = {
      type,

      id: this.socket.id,
      name: this.instanceName,
      instanceId: this.appInstanceId,
    };
    if (state) {
      message.payload =
        type === 'ERROR'
          ? state
          : (0, _jsan.stringify)(
              (0, _utils.filterState)(
                state,
                type,
                this.filters,
                this.stateSanitizer,
                this.actionSanitizer,
                nextActionId,
              ),
            );
    }
    if (type === 'ACTION') {
      message.action = (0, _jsan.stringify)(
        !this.actionSanitizer
          ? action
          : this.actionSanitizer(action.action, nextActionId - 1),
      );
      message.isExcess = this.isExcess;
      message.nextActionId = nextActionId;
    } else if (action) {
      message.action = action;
    }

    void this.socket.transmit(this.socket.id ? 'log' : 'log-noid', message);
  }
  dispatchRemotely(action) {
    try {
      const result = (0, _utils.evalAction)(action, this.actionCreators);
      this.store.dispatch(result);
    } catch (e) {
      this.relay('ERROR', e.message);
    }
  }
  init(options) {
    this.instanceName = options.name;
    this.appInstanceId = getRandomId();
    const {blacklist, whitelist, denylist, allowlist} = options.filters || {};
    this.filters = (0, _utils.getLocalFilter)({
      actionsDenylist:
        denylist ??
        options.actionsDenylist ??
        blacklist ??
        options.actionsBlacklist,
      actionsAllowlist:
        allowlist ??
        options.actionsAllowlist ??
        whitelist ??
        options.actionsWhitelist,
    });
    if (options.port) {
      this.socketOptions = {
        port: options.port,
        hostname: options.hostname || 'localhost',
        secure: options.secure,
      };
    } else {
      this.socketOptions = _constants.defaultSocketOptions;
    }
    this.suppressConnectErrors =
      options.suppressConnectErrors !== undefined
        ? options.suppressConnectErrors
        : true;
    this.startOn = str2array(options.startOn);
    this.stopOn = str2array(options.stopOn);
    this.sendOn = str2array(options.sendOn);
    this.sendOnError = options.sendOnError;
    if (this.sendOn || this.sendOnError) {
      this.sendTo =
        options.sendTo ||
        `${this.socketOptions.secure ? 'https' : 'http'}://${
          this.socketOptions.hostname
        }:${this.socketOptions.port}`;
      this.instanceId = options.id;
    }
    if (this.sendOnError === 1) {
      (0, _utils.catchErrors)(this.sendError);
    }
    if (options.actionCreators) {
      this.actionCreators = () =>
        (0, _utils.getActionsArray)(options.actionCreators);
    }
    this.stateSanitizer = options.stateSanitizer;
    this.actionSanitizer = options.actionSanitizer;
  }
  login() {
    void (async () => {
      try {
        const channelName = await this.socket.invoke('login', 'master');
        this.channel = channelName;
        let consumer = this.socket.subscribe(channelName).createConsumer();
        while (true) {
          const {value: data, done} = await consumer.next();
          if (done) {
            break;
          }

          // for await (const data of this.socket.subscribe(channelName)) {
          this.handleMessages(data);
        }
      } catch (error) {
        console.log(error);
      }
    })();
    this.started = true;
    this.relay('START');
  }

  handleChange(state, liftedState, maxAge) {
    if (this.checkForReducerErrors(liftedState)) {
      return;
    }
    if (this.lastAction === 'PERFORM_ACTION') {
      const nextActionId = liftedState.nextActionId;
      const liftedAction = liftedState.actionsById[nextActionId - 1];
      if ((0, _utils.isFiltered)(liftedAction.action, this.filters)) {
        return;
      }
      this.relay('ACTION', state, liftedAction, nextActionId);
      if (!this.isExcess && maxAge) {
        this.isExcess = liftedState.stagedActionIds.length >= maxAge;
      }
    } else {
      if (this.lastAction === 'JUMP_TO_STATE') {
        return;
      }
      if (this.lastAction === 'PAUSE_RECORDING') {
        this.paused = liftedState.isPaused;
      } else if (this.lastAction === 'LOCK_CHANGES') {
        this.locked = liftedState.isLocked;
      }
      if (this.paused || this.locked) {
        if (this.lastAction) {
          this.lastAction = undefined;
        } else {
          return;
        }
      }
      this.relay(
        'STATE',
        (0, _utils.filterStagedActions)(liftedState, this.filters),
      );
    }
  }
}
var _default = options => new DevToolsEnhancer().enhance(options);
exports.default = _default;
const compose = options =>
  function () {
    for (
      var _len = arguments.length, funcs = new Array(_len), _key = 0;
      _key < _len;
      _key++
    ) {
      funcs[_key] = arguments[_key];
    }
    return function () {
      const devToolsEnhancer = new DevToolsEnhancer();
      function preEnhancer(createStore) {
        return (reducer, preloadedState) => {
          devToolsEnhancer.store = createStore(reducer, preloadedState);
          return {
            ...devToolsEnhancer.store,
            dispatch: action =>
              devToolsEnhancer.locked
                ? action
                : devToolsEnhancer.store.dispatch(action),
          };
        };
      }
      for (
        var _len2 = arguments.length, args = new Array(_len2), _key2 = 0;
        _key2 < _len2;
        _key2++
      ) {
        args[_key2] = arguments[_key2];
      }
      return [preEnhancer, ...funcs].reduceRight(
        (composed, f) => f(composed),
        devToolsEnhancer.enhance(options)(...args),
      );
    };
  };
function composeWithDevTools() {
  for (
    var _len3 = arguments.length, funcs = new Array(_len3), _key3 = 0;
    _key3 < _len3;
    _key3++
  ) {
    funcs[_key3] = arguments[_key3];
  }
  if (funcs.length === 0) {
    return new DevToolsEnhancer().enhance();
  }
  if (funcs.length === 1 && typeof funcs[0] === 'object') {
    return compose(funcs[0]);
  }
  return compose({})(...funcs);
}


LunatiqueCoder avatar Jul 01 '23 16:07 LunatiqueCoder

☝️ Also created a PR to fix this so we won't need to patch.

LunatiqueCoder avatar Jul 03 '23 02:07 LunatiqueCoder

Did this PR fix Hermes support for anyone? Unfortunately, it did not for me.

I see, that async iterators are now transpiled by babel, but it is still not working with Hermes. When using @redux-devtools/[email protected] with React Native on Hermes, I get the following error: "TypeError: Object is not async iterable". This stances from the transpiled async iterator implementation, which seems to have problems here.

pmk1c avatar Mar 28 '24 14:03 pmk1c

Yeah, looks like others are getting that error as well. PRs are welcome, I'm not sure why it's not working if it's getting transpiled.

Methuselah96 avatar Mar 28 '24 14:03 Methuselah96

Would a PR where the use of for await is replaced get accepted? Something like this, seems to work for me:

let consumer = this.socket.listener(channelName).createConsumer();
while (true) {
  const {value: data, done} = await consumer.next();
  if (done) break;
  // do stuff
}

Edit: This seems to be a problem with the socketcluster-client implementation: https://github.com/SocketCluster/socketcluster-client/issues/150

pmk1c avatar Mar 28 '24 15:03 pmk1c

I would prefer to get to the root issue of why the current solution is not working, but may accept a workaround if necessary. My schedule's been pretty packed, but it should free up soon to give me time to look into this if someone doesn't beat me to it.

Methuselah96 avatar Mar 28 '24 15:03 Methuselah96

https://github.com/babel/babel/issues/7467 may be related.

Methuselah96 avatar Mar 28 '24 15:03 Methuselah96

I wonder if TypeScript can transpile for await correctly. I've been thinking of moving to straight TypeScript transpilation and cutting out Babel entirely. Based on this test though it looks like it also uses Symbol.asyncIterator which may be the issue. 🤔

Methuselah96 avatar Mar 28 '24 15:03 Methuselah96

This looks related and the TypeScript release notes mention that you need to polyfill Symbol.asyncIterator for it to work. 🤔

Methuselah96 avatar Mar 28 '24 16:03 Methuselah96

https://github.com/reduxjs/redux-devtools/pull/1642 may fix it. I'll release it as a patch since it seems safe enough and you can let me know whether it works or not.

Methuselah96 avatar Mar 28 '24 16:03 Methuselah96

@redux-devtools@[email protected] has been published with the above fix. Let me know if it fixes the issue for you.

Methuselah96 avatar Mar 28 '24 16:03 Methuselah96

Unfortunately I'm still getting the same error, although now it seems as if it only happens for the this.socket.subscribe(…) and not all for await-Blocks. Since I am getting the connected to remotedev-server log.

When I add the Symbol.asyncIterator-Polyfill to the top of my App-Entrypoint, it seems to work. I guess it has something to do with require("socketcluster-client") happening before Symbol.asyncIterator = Symbol.asyncIterator || Symbol.for('Symbol.asyncIterator');, so the definition of the iterator does not work for some cases.

Nevertheless, when I add the Symbol.asyncIterator-Polyfill to the top of my App-Entrypoint, I get rid of the error, but connecting to Redux Devtools still doesn't seem to work. I can see my App sending the "START"-Message, but I cannot see any response coming from the Devtools-server. So the Remote Devtools don't start tracking anything. This should be some different error though, not related to the for await-Problem, I guess...

pmk1c avatar Mar 29 '24 22:03 pmk1c

I tried moving the Symbol.asyncIterator-Polyfill into the index.ts of @redux-devtools/remote, but it still didn't work.

I guess for now the easiest way, is to document, that users need to put: Symbol.asyncIterator = Symbol.asyncIterator || Symbol.for('Symbol.asyncIterator'); into their App-Entrypoint for this library to work on Hermes. 🤷

pmk1c avatar Mar 29 '24 23:03 pmk1c

Thanks for the feedback, I'll try to take a closer look at this when my free time opens up.

Methuselah96 avatar Mar 30 '24 03:03 Methuselah96

Hi

I just want to mention that the fix in @redux-devtools/[email protected].

causes:

Cannot assign to read only property 'asyncIterator' of function 'function Symbol() { [native code] }'

In electron.

Rolling back to 0.9.1 fixes the error.

timhaak avatar Mar 31 '24 19:03 timhaak

Last version doesn't work for me. But if install 0.8.0 and apply patch it works perfectly.

evgenyshenets91 avatar Apr 19 '24 11:04 evgenyshenets91

Hi guys!

I've followed and tracked the issue from these tickets into https://github.com/endojs/endo/pull/2220 or https://github.com/facebook/hermes/issues/1389.

Do you have an idea @leotm of the configuration we need on this babel.config.json to have a proper way to have a functional Symbol.asyncInterator working?

YoshiYo avatar May 03 '24 09:05 YoshiYo

you can't configure babel when bundling for Hermes. It's hardcoded somewhere in bundler. That's why i created a ticket requesting changes in source code long ago

skurgansky-sugarcrm avatar May 03 '24 09:05 skurgansky-sugarcrm

I was able to fix this issue by adding this index.js in my Expo project. I needed to use require instead of import to make sure bundling doesn't move the asyncIterator-fix after the require-statements.

Quite a weird work-around, but it helps in my case. This should work in a bare React Native project as well.

// index.js
Symbol.asyncIterator ??= Symbol.for("Symbol.asyncIterator");

require("expo").registerRootComponent(require("./App").default);

pmk1c avatar May 03 '24 18:05 pmk1c