reduxjs / redux-devtools

DevTools for Redux with hot reloading, action replay, and customizable UI
http://youtube.com/watch?v=xsSnOQynTHs
MIT License
14.01k stars 1.16k forks source link

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

Open skurgansky-sugarcrm opened 1 year ago

skurgansky-sugarcrm commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago
"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 commented 1 year ago

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 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 ```javascript '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 commented 1 year ago

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

pmk1c commented 6 months ago

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/remote@0.9.1 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 commented 6 months ago

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.

pmk1c commented 6 months ago

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

Methuselah96 commented 6 months ago

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 commented 6 months ago

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

Methuselah96 commented 6 months ago

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 commented 6 months ago

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

Methuselah96 commented 6 months ago

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 commented 6 months ago

@redux-devtools@remote@0.9.2 has been published with the above fix. Let me know if it fixes the issue for you.

pmk1c commented 6 months ago

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 commented 6 months ago

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 commented 6 months ago

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

timhaak commented 6 months ago

Hi

I just want to mention that the fix in @redux-devtools/remote@0.9.2.

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 commented 5 months ago

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

YoshiYo commented 5 months ago

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?

skurgansky-sugarcrm commented 5 months ago

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 commented 5 months ago

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