Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React-native Hermes app has an issue with for await #1382

Open
skurgansky-sugarcrm opened this issue Apr 2, 2023 · 21 comments · Fixed by #1496 or #1642
Open

React-native Hermes app has an issue with for await #1382

skurgansky-sugarcrm opened this issue Apr 2, 2023 · 21 comments · Fixed by #1496 or #1642

Comments

@skurgansky-sugarcrm
Copy link

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 ?

@LunatiqueCoder
Copy link

LunatiqueCoder commented Jul 1, 2023

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.

@skurgansky-sugarcrm
Copy link
Author

skurgansky-sugarcrm commented Jul 1, 2023

"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);
}

@LunatiqueCoder
Copy link

LunatiqueCoder commented Jul 1, 2023

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
Copy link

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

@pmk1c
Copy link

pmk1c commented Mar 28, 2024

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.

@Methuselah96
Copy link
Member

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 Methuselah96 reopened this Mar 28, 2024
@pmk1c
Copy link

pmk1c commented Mar 28, 2024

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: SocketCluster/socketcluster-client#150

@Methuselah96
Copy link
Member

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
Copy link
Member

babel/babel#7467 may be related.

@Methuselah96
Copy link
Member

Methuselah96 commented Mar 28, 2024

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
Copy link
Member

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

@Methuselah96
Copy link
Member

#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
Copy link
Member

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

@pmk1c
Copy link

pmk1c commented Mar 29, 2024

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
Copy link

pmk1c commented Mar 29, 2024

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. 🤷

@Methuselah96 Methuselah96 reopened this Mar 30, 2024
@Methuselah96
Copy link
Member

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

@timhaak
Copy link

timhaak commented Mar 31, 2024

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.

@evgenyshenets91
Copy link

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

@YoshiYo
Copy link

YoshiYo commented May 3, 2024

Hi guys!

I've followed and tracked the issue from these tickets into endojs/endo#2220 or facebook/hermes#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?

@skurgansky-sugarcrm
Copy link
Author

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

@pmk1c
Copy link

pmk1c commented May 3, 2024

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);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
7 participants