/
githubmirror
/
wp-calypso
Обзор
Документация
Войти
/
githubmirror
/
wp-calypso
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
trunk
client/state/initial-state.js
300 строк
10 KB
Griffith Chen
Lodash: replace map() with native Array.prototype.map (#112102)
30 июн 2026, 06:44
Не верифицирован
30 июн 2026, 06:44
0dc1083
Код
Авторство
О чём код?
import config from '@automattic/calypso-config'; import { pick } from '@automattic/js-utils'; import { throttle } from '@wordpress/compose'; import debugModule from 'debug'; import { setStoredItem } from 'calypso/lib/browser-storage'; import { isSupportSession } from 'calypso/lib/user/support-user-interop'; import { APPLY_STORED_STATE } from './action-types'; import { MAX_AGE, SERIALIZE_THROTTLE, WAS_STATE_RANDOMLY_CLEARED_KEY } from './constants'; import { clearPersistedState, getPersistedStateItem, storePersistedStateItem, } from './persisted-state'; import { serialize, deserialize } from './utils'; /** * Module variables */ const debug = debugModule( 'calypso:state' ); // Store the timestamp at which the module loads as a proxy for the timestamp // when the server data (if any) was generated. const bootTimestamp = Date.now(); function deserializeStored( reducer, stored ) { const { _timestamp, ...state } = stored; return deserialize( reducer, state ); } export function shouldPersist() { return ! isSupportSession(); } /** * Determines whether to add "sympathy" by randomly clearing out persistent * browser state and loading without it * * Can be overridden on the command-line with two flags: * - ENABLE_FEATURES=force-sympathy yarn start (always sympathize) * - ENABLE_FEATURES=no-force-sympathy yarn start (always prevent sympathy) * * If both of these flags are set, then `force-sympathy` takes precedence. * @returns {boolean} Whether to clear persistent state on page load */ function shouldAddSympathy() { // If `force-sympathy` flag is enabled, always clear persistent state. if ( config.isEnabled( 'force-sympathy' ) ) { return true; } // If `no-force-sympathy` flag is enabled, never clear persistent state. if ( config.isEnabled( 'no-force-sympathy' ) ) { return false; } // Otherwise, in development mode, clear persistent state 25% of the time. if ( 'development' === process.env.NODE_ENV && Math.random() < 0.25 ) { return true; } // Otherwise, do not clear persistent state. return false; } // Verifies that the server-provided Redux state isn't too old. // This is rarely a problem, and only comes up in extremely long-lived sessions. function verifyBootTimestamp() { return bootTimestamp + MAX_AGE > Date.now(); } // Verifies that the persisted Redux state isn't too old. function verifyStateTimestamp( state ) { return state._timestamp && state._timestamp + MAX_AGE > Date.now(); } function getPersistenceKey( userId, subkey ) { return 'redux-state-' + ( userId ?? 'logged-out' ) + ( subkey ? ':' + subkey : '' ); } async function persistentStoreState( reduxStateKey, storageKey, state, _timestamp ) { if ( storageKey !== 'root' ) { reduxStateKey += ':' + storageKey; } const newState = { ...state, _timestamp }; await storePersistedStateItem( reduxStateKey, newState ); } export function persistOnChange( reduxStore, currentUserId ) { if ( ! shouldPersist() ) { return () => {}; } let prevState = null; const throttledSaveState = throttle( function () { const state = reduxStore.getState(); if ( state === prevState ) { return; } prevState = state; const serializedState = serialize( reduxStore.getCurrentReducer(), state ); const _timestamp = Date.now(); const reduxStateKey = getPersistenceKey( currentUserId ); const storeTasks = Object.entries( serializedState.get() ).map( ( [ storageKey, data ] ) => persistentStoreState( reduxStateKey, storageKey, data, _timestamp ) ); Promise.all( storeTasks ).catch( ( setError ) => debug( 'failed to set redux-store state', setError ) ); }, SERIALIZE_THROTTLE, { leading: false, trailing: true } ); if ( typeof window !== 'undefined' ) { window.addEventListener( 'beforeunload', throttledSaveState.flush ); } const unsubscribe = reduxStore.subscribe( throttledSaveState ); return () => { if ( typeof window !== 'undefined' ) { window.removeEventListener( 'beforeunload', throttledSaveState.flush ); } unsubscribe(); throttledSaveState.cancel(); }; } // Retrieve the initial state for the application, combining it from server and // local persistence (server data gets priority). // This function only handles legacy Redux state for the monolithic root reducer // `loadPersistedState` must have completed first. export function getInitialState( initialReducer, currentUserId ) { const storedState = getInitialPersistedState( initialReducer, currentUserId ); const serverState = getInitialServerState( initialReducer ); return { ...storedState, ...serverState }; } // Retrieve the initial bootstrapped state from a server-side render. // This function only handles legacy Redux state for the monolithic root reducer function getInitialServerState( initialReducer ) { if ( typeof window !== 'object' || ! window.initialReduxState || isSupportSession() ) { return null; } const serverState = deserializeStored( initialReducer, window.initialReduxState ); return pick( serverState, Object.keys( window.initialReduxState ) ); } // Retrieve the initial persisted state from the cached local client data. // This function only handles legacy Redux state for the monolithic root reducer // `loadPersistedState` must have completed first. function getInitialPersistedState( initialReducer, currentUserId ) { if ( ! shouldPersist() ) { return null; } if ( 'development' === process.env.NODE_ENV ) { window.resetState = () => clearPersistedState().then( () => window.location.reload( true ) ); if ( shouldAddSympathy() ) { // eslint-disable-next-line no-console console.log( '%cSkipping initial state rehydration. (This runs during random page requests in the Calypso development environment, to simulate loading the application with an empty cache.)', 'font-size: 14px; color: red;' ); clearPersistedState(); /** * Decide whether to save a flag that indicates whether * the persisted state was randomly cleared */ if ( config.isEnabled( 'force-sympathy' ) ) { /** * If we're forcing the state-clearing we don't * have to announce it, because someone intentionally * forced it and should know why it's cleared. */ } else { /** * If state-clearing wasn't forced and we still cleared persisted state * this means it happened randomly. Consequently, save a flag * so that we can notify the developer once the UI is mounted */ setStoredItem( WAS_STATE_RANDOMLY_CLEARED_KEY, true ); } return null; } } let initialStoredState = getStateFromPersistence( initialReducer, undefined, currentUserId ); const storageKeys = [ ...initialReducer.getStorageKeys() ]; function loadReducerState( { storageKey, reducer } ) { const storedState = getStateFromPersistence( reducer, storageKey, currentUserId ); if ( storedState ) { initialStoredState = initialReducer( initialStoredState, { type: APPLY_STORED_STATE, storageKey, storedState, } ); } } for ( const item of storageKeys ) { loadReducerState( item ); } return initialStoredState; } // Retrieve the initial state for a portion of state, from persisted data alone. // This function handles both legacy and modularized Redux state. // `loadPersistedState` must have completed first. function getStateFromPersistence( reducer, subkey, currentUserId ) { const reduxStateKey = getPersistenceKey( currentUserId, subkey ); const state = getPersistedStateItem( reduxStateKey ); return deserializeState( subkey, state, reducer, false ); } // Retrieve the initial state for a portion of state, choosing the freshest // between server and local persisted data. // This function handles both legacy and modularized Redux state. // `loadPersistedState` must have completed first. export const getStateFromCache = ( currentUserId ) => ( reducer, subkey ) => { let serverState = null; if ( subkey && typeof window !== 'undefined' ) { serverState = window.initialReduxState?.[ subkey ] ?? null; } let persistedState = getPersistedStateItem( getPersistenceKey( currentUserId, subkey ) ); // Special case for handling signup flows where the user logs in halfway through. if ( ! persistedState && subkey === 'signup' ) { persistedState = getPersistedStateItem( getPersistenceKey( null, subkey ) ); // If we are logged in, we no longer need the 'user' step in signup progress tree. if ( persistedState && persistedState.progress && persistedState.progress.user ) { delete persistedState.progress.user; } debug( 'fetched signup state from logged out state', persistedState ); } // Default to server state, if it exists. let useServerState = serverState !== null; // Replace with persisted state if it's fresher. if ( persistedState?._timestamp && persistedState._timestamp > bootTimestamp ) { useServerState = false; } return deserializeState( subkey, useServerState ? serverState : persistedState, reducer, useServerState ); }; // Deserialize a portion of state. // This function handles both legacy and modularized Redux state. function deserializeState( subkey, state, reducer, isServerState = false ) { const origin = isServerState ? 'server' : 'persisted'; try { if ( state === null ) { debug( `Redux state for subkey '${ subkey }' not found in ${ origin } data` ); return null; } const validTimestamp = isServerState ? verifyBootTimestamp() : verifyStateTimestamp( state ); if ( ! validTimestamp ) { debug( `${ origin } Redux state is too old, dropping` ); return null; } const deserializedState = deserializeStored( reducer, state ); if ( ! deserializedState ) { debug( `${ origin } Redux state failed to deserialize, dropping` ); return null; } return deserializedState; } catch ( error ) { debug( `error while loading ${ origin } Redux state:`, error ); return null; } }