Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle missing Web Crypto API in non-HTTPS contexts when generating the already-submitted sessionStorage key #1911

Merged
merged 4 commits into from
Mar 12, 2025

Conversation

ShyamGadde
Copy link
Contributor

@ShyamGadde ShyamGadde commented Mar 10, 2025

Summary

Fixes #1904

Relevant technical choices

  • Returns null instead of blocking detection when the Web Crypto API is unavailable, allowing the script to continue running while still tracking session data when possible.
  • Logs a warning when the Web Crypto API is unavailable, advising users to use HTTPS or localhost for testing.
  • Catches errors during hashing and logs them instead of failing outright.
  • Ensures sessionStorage is only accessed if a valid key is generated.

@ShyamGadde ShyamGadde added [Type] Bug An existing feature is broken [Plugin] Optimization Detective Issues for the Optimization Detective plugin labels Mar 10, 2025
Comment on lines 166 to 172
// eslint-disable-next-line no-console
console.warn(
'[Optimization Detective] Web Crypto API is not available. This API is only available in secure contexts (HTTPS). Detection cannot proceed. If you are testing locally, ensure you use HTTPS or run on localhost.'
);
throw new Error(
'Web Crypto API is unavailable in this context. Try using HTTPS or localhost.'
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this reuse the logger?

Suggested change
// eslint-disable-next-line no-console
console.warn(
'[Optimization Detective] Web Crypto API is not available. This API is only available in secure contexts (HTTPS). Detection cannot proceed. If you are testing locally, ensure you use HTTPS or run on localhost.'
);
throw new Error(
'Web Crypto API is unavailable in this context. Try using HTTPS or localhost.'
);
error(
'[Optimization Detective] Web Crypto API is not available. This API is only available in secure contexts (HTTPS). Detection cannot proceed. If you are testing locally, ensure you use HTTPS or run on localhost.'
);
return;

) {
log(
'The current client session already submitted a fresh URL Metric for this URL so a new one will not be collected now.'
let alreadySubmittedSessionStorageKey;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this change needed? The above check for crypto.subtle should mean this change won't be needed, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, although we check for crypto.subtle, I think the try/catch is still necessary.

If we were to just return early from getAlreadySubmittedSessionStorageKey when crypto.subtle is unavailable (instead of throwing an error), the function would return undefined and the key would be undefined for all URLs the user visits. This could lead to misleading messages like "The current client session already submitted a fresh URL Metric..." before the freshnessTTL expires.

Copy link
Contributor Author

@ShyamGadde ShyamGadde Mar 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option would be to just throw a simple error when Web Crypto API is not available in the getAlreadySubmittedSessionStorageKey function (without logging):

throw new Error( 'Web Crypto API is unavailable' );

And then handle the detailed messaging in the catch block inside detect:

catch (err) {
  if ( err.message === 'Web Crypto API is unavailable' ) {
    error(
      'Unable to create session storage key: Web Crypto API is not available. This API is only available in secure contexts (HTTPS). Detection cannot proceed. If you are testing locally, ensure you use HTTPS or run on localhost.'
    );
  } else {
    error('Unable to create session storage key: ' + err.message);
  }
  return;
}

This approach also avoids duplicate error messages.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, but why would crypto.subtle need the try/catch?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, but why would crypto.subtle need the try/catch?

I'm sorry for not being clear. I wasn't suggesting adding a try/catch in the getAlreadySubmittedSessionStorageKey function itself. Instead, I was referring to the try/catch block I added in the detect function where we call await getAlreadySubmittedSessionStorageKey().

The check for crypto.subtle is in the getAlreadySubmittedSessionStorageKey function, which throws an error when it's not available. Then, that error is caught in the detect function's try/catch block.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've implemented this approach in a66325a to illustrate what I was describing.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for that. Here's another iteration on top of your suggestion: what if we have it so that the function just returns a constant string in the case of an error? This would essentially be bringing the functionality back to how it behaved before the URL hashing was introduced. Oh wait, actually that functionality is still in place:

if ( isStorageLocked( getCurrentTime(), storageLockTTL ) ) {
warn( 'Aborted detection due to storage being locked.' );
return;
}

So how about it just return null to indicate that this isn't available? There is already storage locking going on with a session storage key, so this is basically a secondary (redundant?) lock.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's what I have in mind: d5679ec

This will ensure that URL Metrics can continue to be collected on HTTP sites. The already-submitted "lock" is just intended to increase the diversity of the URL Metrics being collected, so if it's not available it's not a big deal. Better to skip it altogether in favor of collecting URL Metrics as opposed to doing nothing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for iterating on this! That approach makes a lot of sense—returning null instead of blocking detection entirely keeps things functional while still maintaining some level of session tracking. I hadn't fully considered that before.

Signed-off-by: Shyamsundar Gadde <[email protected]>
) {
log(
'The current client session already submitted a fresh URL Metric for this URL so a new one will not be collected now.'
let alreadySubmittedSessionStorageKey;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for that. Here's another iteration on top of your suggestion: what if we have it so that the function just returns a constant string in the case of an error? This would essentially be bringing the functionality back to how it behaved before the URL hashing was introduced. Oh wait, actually that functionality is still in place:

if ( isStorageLocked( getCurrentTime(), storageLockTTL ) ) {
warn( 'Aborted detection due to storage being locked.' );
return;
}

So how about it just return null to indicate that this isn't available? There is already storage locking going on with a session storage key, so this is basically a secondary (redundant?) lock.

@ShyamGadde ShyamGadde marked this pull request as ready for review March 12, 2025 02:26
@ShyamGadde ShyamGadde requested a review from felixarntz as a code owner March 12, 2025 02:26
Copy link

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: ShyamGadde <[email protected]>
Co-authored-by: westonruter <[email protected]>
Co-authored-by: swissspidy <[email protected]>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

Copy link
Member

@westonruter westonruter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for iterating on this with me!

@westonruter westonruter requested a review from swissspidy March 12, 2025 05:35
urlMetricGroupStatus.minimumViewportWidth,
urlMetricGroupStatus.maximumViewportWidth || '',
].join( '-' );
if ( ! window.crypto || ! window.crypto.subtle ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: only checking window.crypto should suffice. There's no combination where the former is available but the latter isn't.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So like so?

Suggested change
if ( ! window.crypto || ! window.crypto.subtle ) {
if ( ! window.crypto ) {

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevertheless someone could try to polyfill crypto.getRandomValues() but not define crypto.subtle.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So like so?

Yes that's what I meant.

Nevertheless someone could try to polyfill crypto.getRandomValues() but not define crypto.subtle.

Sounds unlikely to me but if we are concerned about that then we should keep the check.

@westonruter westonruter merged commit 8efc280 into WordPress:trunk Mar 12, 2025
18 of 19 checks passed
@westonruter westonruter changed the title Handle missing Web Crypto API in non-HTTPS contexts Handle missing Web Crypto API in non-HTTPS contexts when generating the already-submitted sessionStorage key Mar 17, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Plugin] Optimization Detective Issues for the Optimization Detective plugin [Type] Bug An existing feature is broken
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Optimization Detective not working on non-HTTPS site
3 participants