fix: resolve browser extension compatibility issue in ShopifyCustomer…#3576
fix: resolve browser extension compatibility issue in ShopifyCustomer…#3576frankl1nrg wants to merge 2 commits into
Conversation
…Privacy component Added defensive checks to handle cases where window.Shopify is defined as non-configurable by browser extensions, preventing "Cannot redefine property: Shopify" errors. Implemented polling fallback to ensure proper functionality of Hydrogen apps with extensions.
|
I have signed the CLA! |
andguy95
left a comment
There was a problem hiding this comment.
@frankl1nrg The entire fallback code path added in this PR is completely untested. Can you please add tests coverage to this scenario please.
| window, | ||
| 'Shopify', | ||
| ); | ||
| if (shopifyDescriptor && !shopifyDescriptor.configurable) { |
There was a problem hiding this comment.
blocking: the fallback block skips two things the normal path does:
setTrackingConsentoverride - the normal path (~15 lines below) wraps this to inject the headless storefront config. In the fallback, consumers get the raw CDN version, which means consent calls might silently fail for headless stores.backendConsentEnabledstub - the normal path (~35 lines below) installs{backendConsentEnabled: true}so the CDN reads the right flag before assigning the full API. The fallback skips this.
Also - the PR description says "App loads normally with warning in console" but there's no console.warn here.
I think the polling callback should apply both overrides when it detects customerPrivacy, and we should add a console.warn at the top of this block.
| 'Shopify', | ||
| ); | ||
| if (shopifyDescriptor && !shopifyDescriptor.configurable) { | ||
| customShopify = window.Shopify || undefined; |
There was a problem hiding this comment.
nit: customShopify was already assigned to window.Shopify || undefined on line 341. This is a no-op, I think we can remove it.
| customShopify = window.Shopify || undefined; | |
| const pollForCustomerPrivacy = setInterval(() => { |
| setLoaded.customerPrivacy(); | ||
| clearInterval(pollForCustomerPrivacy); | ||
| } | ||
| }, 100); |
There was a problem hiding this comment.
non-blocking: the interval runs indefinitely until customerPrivacy appears or the component unmounts - if the CDN script fails to load for any reason, this runs for the page's entire lifetime. I think we should add a max retry count (e.g. 100 tries = 10 seconds at 100ms).
| @@ -340,6 +340,21 @@ export function useCustomerPrivacy(props: CustomerPrivacyApiProps) { | |||
| let customShopify: {customerPrivacy: CustomerPrivacy} | undefined | object = | |||
There was a problem hiding this comment.
question Re: line 325 - wouldn't window.privacyBanner have the same potential issue. If an extension defines it as non-configurable before Hydrogen initializes, the Object.defineProperty on line 325 might throw too?
| } | ||
|
|
||
| // monitor for when window.Shopify = {} is first set | ||
| Object.defineProperty(window, 'Shopify', { |
There was a problem hiding this comment.
question / possible improvement?: Instead of pre-checking for the descriptor can we try catch the Object.defineProperty(window, 'Shopify', { /* ...code below */ });
Then in the catch intercept like you did above with Object.defineProperty(window.Shopify, 'customerPrivacy', {...}
Something like:
try {
// Normal path - intercept window.Shopify via property descriptor
Object.defineProperty(window, 'Shopify', { ....})
} catch {
console.warn("[h2] browser extension detected, using compatibility mode")
// Intercept one level deeper Writing to window.Shopify still works via the extension's setter,
// so we can still install the backendConsentEnabled stub
window.Shopify.backendConsentEnabled = true
attempts = 0
poll every 100ms:
if window.Shopify.customerPrivacy exists:
stop polling
wrap customerPrivacy.setTrackingConsent with headless config pre-applied
// window.Shopify.customerPrivacy is a plain property on the inner object,
// so we can still redefine it with our wrapped version
Object.defineProperty(window.Shopify, 'customerPrivacy', { wrapped version })
call setLoaded.customerPrivacy()
else if attempts > 100:
stop polling
console.warn("[h2] customerPrivacy never appeared")
return cleanup: clear interval
}
|
This pull request has been marked as stale due to inactivity for 60 days. If no further activity occurs, it will be closed in 7 days. |
|
this seems to be still an issue. |
WHY are these changes introduced?
Fixes #3575
Browser extensions like Urban VPN define
window.Shopifyas non-configurable before Hydrogen initializes, causing aTypeError: Cannot redefine property: Shopifycrash in production. This affects real users and makes Hydrogen apps unusable when common browser extensions are installed.WHAT is this pull request doing?
ShopifyCustomerPrivacy.tsxwindow.Shopifyis already defined as non-configurableKey changes:
Object.getOwnPropertyDescriptor(window, "Shopify")before attempting redefinitionsetIntervalpolling to monitorcustomerPrivacywhen property is non-configurableHOW to test your changes?
Reproduction test:
TypeError: Cannot redefine property: ShopifyLocal testing: