Skip to content

Commit

Permalink
feat: improve state lock-out with reflection (#3924)
Browse files Browse the repository at this point in the history
An improvement/iteration on #3890:
- Hide the state symbol from all methods relying on
`[[OwnPropertyKeys]]` [^1]
- Do some error handling to help users if they're facing an error due to
a double-load of Headless.

Further improvements to consider for 'next-gen' controller:
- Add safeguards for cross-contaminations on controller instantiation,
see [POC](https://louis-bompart.github.io/automatic-eureka/)
([source](https://github.com/louis-bompart/automatic-eureka/tree/main))
 
[^1]:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/ownKeys

---------

Co-authored-by: Frederic Beaudoin <fbeaudoin@coveo.com>
  • Loading branch information
louis-bompart and fbeaudoincoveo authored May 14, 2024
1 parent 469ca02 commit 7e1506d
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 4 deletions.
6 changes: 3 additions & 3 deletions packages/headless/src/app/commerce-engine/commerce-engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
ExternalEngineOptions,
} from '../engine';
import {buildLogger} from '../logger';
import {stateKey} from '../state-key';
import {redactEngine, stateKey} from '../state-key';
import {buildThunkExtraArguments} from '../thunk-extra-arguments';
import {
CommerceEngineConfiguration,
Expand Down Expand Up @@ -127,7 +127,7 @@ export function buildCommerceEngine(
engine.dispatch(setItems(options.configuration.cart.items));
}

return {
return redactEngine({
...engine,

get [stateKey]() {
Expand All @@ -147,7 +147,7 @@ export function buildCommerceEngine(
const action = executeSearch();
internalEngine.dispatch(action);
},
};
});
}

function validateConfiguration(
Expand Down
26 changes: 25 additions & 1 deletion packages/headless/src/app/state-key.ts
Original file line number Diff line number Diff line change
@@ -1 +1,25 @@
export const stateKey = Symbol('state');
import type {CoreEngineNext} from './engine';

const stateKeyDescription = 'state';
export const stateKey = Symbol(stateKeyDescription);

export const redactEngine = <TEngine extends CoreEngineNext>(
engine: TEngine
): TEngine =>
new Proxy(engine, {
ownKeys(target) {
return Reflect.ownKeys(target).filter((key) => key !== stateKey);
},
get(target, prop, receiver) {
if (
typeof prop === 'symbol' &&
prop.description === stateKeyDescription &&
prop !== stateKey
) {
engine.logger.warn(
"You might be loading Headless twice. Please check your setup.\nIf you are trying to access the inner state... Don't"
);
}
return Reflect.get(target, prop, receiver);
},
});

0 comments on commit 7e1506d

Please sign in to comment.