Сервер предоставил React ExpressJS интерфейс утечки данных пользователей Redux хранилище

У меня есть сервер ExpressJS, который иногда отображает неверные данные пользователя при первоначальном отображении. Смотрите (немного упрощенную) версию ниже.

Проблема в том, что index.ejs Файл часто отображает неверные данные пользователя в reduxState...

Мое замешательство связано с тем, что я ожидал import { store } from 'routes.js' перезаписать магазин как {} с каждым индивидуальным запросом пользователя. Проблема в том, что store становится объединенным магазином для каждого пользователя на сайте, вместе взятых.

Как я могу убедиться, что каждый пользователь видит только свои данные на сайте?

routes.js

// src/routes.js
import React from 'react';
import { createStore, applyMiddleware, compose } from "redux";
import routerConfig from "base/routes/routes";
import thunk from "redux-thunk";
import { rootReducer } from "base/reducers";

let initialState = {};

const store = createStore(
  rootReducer, initialState, compose(applyMiddleware(thunk))
);

const routes = routerConfig(store);

export {store};
export default routes;

server.js

import { store } from 'routes';

let getReduxPromise = (renderProps, request) => {
  let store = require('./routes/index.jsx').store
  let { query, params } = renderProps

  let comp = renderProps.components[renderProps.components.length - 1];

  let at = null;

  if (request && request.cookies && request.cookies.accessToken) {
    at = request.cookies.accessToken
  }

  if (comp.fetchData) {
    return comp.fetchData({ query, params, store, at }).then(response => {
      if (request) {
        if (request.cookies && request.cookies.accessToken && request.cookies.userInfo) {
          store.dispatch(
            actions.auth(request.cookies.userInfo),
            request.cookies.accessToken
          )
        } else {
          store.dispatch(actions.logout())
        }

      }
      return Promise.resolve({response, state: store.getState()})
    });
  } else {
    return Promise.resolve();
  }
}

app.get('*', (request, response) => {
  let htmlFilePath = path.resolve('build/index.html' );
  // let htmlFilePath = path.join(__dirname, '/build', 'index.html');
  let error = () => response.status(404).send('404 - Page not found');
  fs.readFile(htmlFilePath, 'utf8', (err, htmlData) => {
    if (err) {
      console.log('error 1')
      error();
    } else {
      match({routes, location: request.url}, (err, redirect, renderProps) => {
        if (err) {
          console.log('error 2')
          error();
        } else if (redirect) {
          return response.redirect(302, redirect.pathname + redirect.search)
        } else if (renderProps) {
          let parseUrl = request.url.split('/');

          if (request.url.startsWith('/')) {
            parseUrl = request.url.replace('/', '').split('/');
          }

          // User has a cookie, use this to help figure out where to send them.
          if (request.cookies.userInfo) {
            const userInfo = request.cookies.userInfo

            if (parseUrl[0] && parseUrl[0] === 'profile' && userInfo) {
              // Redirect the user to their proper profile.
              if (renderProps.params['id'].toString() !== userInfo.id.toString()) {
                parseUrl[1] = userInfo.id.toString();
                const url = '/' + parseUrl.join('/');
                return response.redirect(url);
              }
            }
          }

          getReduxPromise(renderProps, request).then((initialData) => {
            let generatedContent = initialData.response ? render(request, renderProps, initialData.response) : render(request, renderProps, {});

            const title = initialData.response.seo.title || '';
            const description = initialData.response.seo.description || '';

            var draft = [];

            const currentState =  initialData.state;

            if (currentState) {
              const reduxState = JSON.stringify(currentState, function(key, value) {
                if (typeof value === 'object' && value !== null) {
                  if (draft.indexOf(value) !== -1) {
                    // Circular reference found, discard key
                    return;
                  }
                  // Store value in our collection
                  draft.push(value);
                }
                return value;
              });
              draft = null;

              ejs.renderFile(
                path.resolve('./src/index.ejs' ),
                {
                  jsFile,
                  cssFile,
                  production,
                  generatedContent,
                  reduxState,
                  title,
                  description
                }, {},
                function(err, str) {
                  if (err) {
                    console.log('error 3')
                    console.log(err);
                  }
                  response.status(200).send(str);
                });
            } else {
              console.log('error 4')
              console.log(err)
              error();
            }

          }).catch(err => {
            console.log('error 5')
            console.log(err)
            error();
          });

        } else {
          console.log('error 6')
          console.log(err)
          error();
        }
      });
    }
  })
});

index.ejs

<!DOCTYPE html>
<html lang="en">
  <head>
    <title><%- title %></title>
    <meta name="description" content="<%- description %>"/>
    <link href="<%- cssFile %>" rel="stylesheet"/>
    <script type="text/javascript" charset="utf-8">
      window.__REDUX_STATE__ = <%- reduxState %>;
    </script>
  </head>
  <body>
    <div id="root"><%- generatedContent %></div>
    <script type="text/javascript" src="<%- jsFile %>" defer></script>
  </body>
</html>

пример fetchData функция в компоненте React

ExamplePage.fetchData = function (options) {
  const { store, params, at } = options

  return Promise.all([
    store.dispatch(exampleAction(params.id, ACTION_TYPE, userAccessToken))
  ]).spread(() => {
    let data = {
      seo: {
        title: 'SEO Title',
        description: 'SEO Description'
      }
    }

    return Promise.resolve(data)
  })
}

1 ответ

Решение

Переменные, определенные в области видимости модуля, имеют только одну копию во всей среде выполнения. Это означает, что каждый процесс node.js имеет свою собственную копию, а каждая вкладка / фрейм браузера имеет свою собственную копию. Однако внутри каждой вкладки или каждого процесса есть только одна копия. Это означает, что вы не можете определить свой магазин как const уровня модуля, и у вас все еще есть новое хранилище для каждого пользователя. Вы можете решить это так:

SRC / routes.js

import React from 'react';
import { createStore, applyMiddleware, compose } from "redux";
import routerConfig from "base/routes/routes";
import thunk from "redux-thunk";
import { rootReducer } from "base/reducers";

let initialState = {};

export function newUserEnv() {
  const store = createStore(
    rootReducer, initialState, compose(applyMiddleware(thunk))
  );

  const routes = routerConfig(store);
  return { store, routes };
}

server.js

import { newUserEnv } from 'routes';

let getReduxPromise = (renderProps, request) => {
  const { store } = newUserEnv();
  let { query, params } = renderProps
...

Это создает новое хранилище для каждого запроса и позволяет каждому пользователю иметь свои собственные данные. Обратите внимание, что если вам нужен один и тот же магазин из разных модулей, вам нужно будет его разослать. Ты не можешь просто import { newUserEnv } потому что это создаст новый.

Другие вопросы по тегам