bjoluc / next-redux-cookie-wrapper

Sync a subset of your Redux state with cookies in Next.js :cookie: :sparkles:
MIT License
114 stars 4 forks source link

__NEXT_REDUX_WRAPPER_HYDRATE__ cookie bug #28

Closed zhangwei900808 closed 2 years ago

zhangwei900808 commented 2 years ago

website eg:www.example.com users:User A and User B action:User A login example website and can get cookie,then User B with other PC open browser run example website User A login info appear in User B browser!!! image

zhangwei900808 commented 2 years ago

User B login info must null but B got User A login info why???

zhangwei900808 commented 2 years ago

store.js

export const initStore = configureStore({
  reducer: combinedReducers,
  middleware: (getDefaultMiddleware) => getDefaultMiddleware().prepend(
    nextReduxCookieMiddleware({
      // 是否压缩
      // compress: false,
      subtrees: ["auth.accessToken", "auth.refreshToken", "auth.isLogin", "auth.me"],
    })
  ).concat(logger)
})

authSlice.js

// 初始化数据
const internalInitialState = {
  accessToken: null,
  refreshToken: null,
  me: null,
  errorMsg: null,
  isLogin: false,
  isExpired: false
};

// reducer
export const authSlice = createSlice({
  name: 'auth',
  initialState: internalInitialState,
  reducers: {
    updateAuth(state, action) {
      state.accessToken = action.payload.accessToken;
      state.me = action.payload.me;
    },
    reset: () => internalInitialState,
    setExpired(state, action) {
      state.isExpired = action.payload;
    },
    getRefreshToken(state, action) {
      return state.refreshToken;
    },
    setAuth(state, action) {
      console.log('-----------setAuth-----------', action)
      if (action.payload) {
        state.accessToken = action.payload.accessToken;
        state.refreshToken = action.payload.refreshToken;
        state.isLogin = action.payload.isLogin;
        state.isExpired = action.payload.isExpired;
      }
    }
  },
  extraReducers: {
    // 水合,拿到服务器端的reducer注入到客户端的reducer,达到数据统一的目的
    [HYDRATE]: (state, action) => {
      console.log('-------------------------------------------------------')
      console.log('----------------------AUTH HYDRATE---------------------')
      console.log('-------------------------------------------------------')
      // console.log('HYDRATE', state, action.payload);
      return {
        ...state,
        ...action.payload.auth,
      };
    },
zhangwei900808 commented 2 years ago

@bjoluc I found the reason and recurrent image when I remove User B cookie(no anything) and refresh User A(user A had login ) chrome then refresh User B chrome is appear!!! but When I don't remove User B cookie(has null cookie) and refresh User A(user A had login ) chrome then refresh User B chrome its worked!!!

So the User B cookie must have value in chrome ,else appear this bug how to resolve it ???

bjoluc commented 2 years ago

Hi @zhangwei900808, I'm not sure if I fully get your issue. To me, it sounds like two browser instances on your machine share cookies. Anyway, any behavior across browser tabs is up to your browser or application and not next-redux-cookie-wrapper. I'm going to close this as unrelated. Anyway, best of success resolving it!

zhangwei900808 commented 2 years ago

@bjoluc you dont understand 1、is not share cookie 2、is next-redux-cookie-wrapper bug

eg: User A in Beijing and User B in Shanghai when User A open website and login then next-redux-cookie-wrapper set cookie in chrome when User B open website not login but he got User A cookie in him chrome

so Why appear it???

bjoluc commented 2 years ago

I tend to be very careful myself about calling out bugs in libraries without putting together a minimal reproducible example, and I'm afraid I can't help you without a self-contained repro repository. Get active and track down the cause of the problem, and when you know which component/library causes it, please come back here with a minimal repro repo and I'll be happy to investigate further. You may also take a look here.

zhangwei900808 commented 2 years ago

image @bjoluc

zhangwei900808 commented 2 years ago

image

zhangwei900808 commented 2 years ago

image @bjoluc

zhangwei900808 commented 2 years ago

redux-toolkit-cookie-wrapper-bug @bjoluc You take a look

zhangwei900808 commented 2 years ago

image @bjoluc

bjoluc commented 2 years ago

Thanks for the repro @zhangwei900808. Your problem is unrelated and took some time for me to spot: The behavior you are facing is due to a single shared store instance on the server due to a faulty initStore definition. In your repro you write

export const initStore = configureStore({...});
export const store = wrapMakeStore(() => initStore);
export const wrapper = createWrapper(store, {...});

which should be something along these lines:

const makeStore = () => configureStore({...});
const wrappedMakeStore = wrapMakeStore(makeStore);
export const wrapper = createWrapper(wrappedMakeStore, {...});

or simply as in the demo:

const makeStore = wrapMakeStore(() => configureStore({...}));
export const wrapper = createWrapper(makeStore, {...});

Cheers!

zhangwei900808 commented 2 years ago

@bjoluc It's Worked! I was so sorry for that and very THKS^_^! but that a litter problem that how can I get dispatch in makeStore?

zhangwei900808 commented 2 years ago

when I used below code is have a problem is not got refresh token ,tips me is null!

import axios from 'axios';
import createAuthRefreshInterceptor from "axios-auth-refresh";
import {refreshToken} from '@/store/slices/authSlice'
import {initStore} from '@/store'

const axiosInstance = axios.create({
  baseURL: `${process.env.NEXT_PUBLIC_API_HOST}`,
  withCredentials: false,
});

// refresh token when 401
createAuthRefreshInterceptor(axiosInstance, async failedRequest => {
  const {dispatch} = initStore();
  const res = await dispatch(refreshToken());
  console.log('============createAuthRefreshInterceptor callback=======', res.payload)
  if (res.payload && res.payload.accessToken)
    failedRequest.response.config.headers.Authorization = 'Bearer ' + res.payload.accessToken;
  return Promise.resolve();
});

export default axiosInstance;
export const refreshToken = createAsyncThunk('auth/refreshToken', async (params, thunkAPI) => {
  try {
    const {refreshToken} = thunkAPI.getState().auth;
    if (refreshToken) {
      console.log('==== refreshToken ====', refreshToken)
      const response = await axios.post('/auth/oauth/token', qs.stringify({
        grant_type: 'refresh_token',
        refresh_token: refreshToken
      }));
      const resdata = response.data;
      console.log('====refreshToken resdata ====', resdata)
      if (resdata.access_token) {
        // const refetch = await axios.get('/account/me', {
        //   headers: {Authorization: `Bearer ${resdata.access_token}`},
        // });
        // console.log('====refreshToken return1 ====', resdata)

        return {
          accessToken: resdata.access_token,
          refreshToken: resdata.refresh_token,
          isLogin: true,
          isExpired: false,
          // me: {
          //   name: refetch.data.name,
          //   avatar: refetch.data.avatar
          // }
        };
      } else {
        return thunkAPI.rejectWithValue({errorMsg: response.data.message});
      }
    } else {
      return thunkAPI.rejectWithValue({errorMsg: "no refreshToken"});
    }
  } catch (error) {
    return thunkAPI.rejectWithValue({errorMsg: error.message});
  }
});
zhangwei900808 commented 2 years ago

I created a mine middle and set axios 401 methods , Its Worked!

export const initStore = () => configureStore({
  reducer: rootReducer,
  middleware: (getDefaultMiddleware) => getDefaultMiddleware().prepend(
    nextReduxCookieMiddleware({
      // 是否压缩
      // compress: false,
      subtrees: ["auth.accessToken", "auth.refreshToken", "auth.isLogin", "auth.me"],
    })
  ).concat((store) => (next) => (action) => {
    // refresh token when 401
    createAuthRefreshInterceptor(axiosInstance, async failedRequest => {
      const res = await store.dispatch(refreshToken());
      console.log('============createAuthRefreshInterceptor callback=======', res.payload)
      if (res.payload && res.payload.accessToken)
        failedRequest.response.config.headers.Authorization = 'Bearer ' + res.payload.accessToken;
      return Promise.resolve();
    });
    //自定义中间件作用:如果上面的判断不返回则会报错,所以返回了一个空的自定义中间件
    return next(action);
  }).concat(process.env.NODE_ENV === `development` ? logger : (store) => (next) => (action) => {
    //自定义中间件作用:如果上面的判断不返回则会报错,所以返回了一个空的自定义中间件
    return next(action);
  })
})