Skip to content

Extract Sign in With Google button JS to a module.#12921

Open
JakePT wants to merge 4 commits into
developfrom
enhancement/11950-sign-in-with-google-module
Open

Extract Sign in With Google button JS to a module.#12921
JakePT wants to merge 4 commits into
developfrom
enhancement/11950-sign-in-with-google-module

Conversation

@JakePT

@JakePT JakePT commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

Addresses issue:

Relevant technical choices

I've added @types/google.accounts as a dependency for use in the module.

PR Author Checklist

  • My code is tested and passes existing unit tests.
  • My code has an appropriate set of unit tests which all pass.
  • My code is backward-compatible with WordPress 5.2 and PHP 7.4.
  • My code follows the WordPress coding standards.
  • My code has proper inline documentation.
  • I have added a QA Brief on the issue linked above.
  • I have signed the Contributor License Agreement (see https://cla.developers.google.com/).

Do not alter or remove anything below. The following sections will be managed by moderators only.

Code Reviewer Checklist

  • Run the code.
  • Ensure the acceptance criteria are satisfied.
  • Reassess the implementation with the IB.
  • Ensure no unrelated changes are included.
  • Ensure CI checks pass.
  • Check Storybook where applicable.
  • Ensure there is a QA Brief.
  • Ensure there are no unexpected significant changes to file sizes.

Merge Reviewer Checklist

  • Ensure the PR has the correct target branch.
  • Double-check that the PR is okay to be merged.
  • Ensure the corresponding issue has a ZenHub release assigned.
  • Add a changelog message to the issue.

@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown

🤖 This comment is automatically updated by CI workflows. Each section is managed independently.

🎭 Playwright reports for 7b735dc:

📚 Storybook for 7b735dc:

📦 Build files for 7b735dc:

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the Sign in with Google integration by migrating the inline frontend JavaScript from PHP into a dedicated TypeScript bundle, passing configuration data via a global window object. The review feedback highlights critical issues in the new TypeScript implementation, specifically a regression where missing dataset attributes can overwrite default button options with undefined, and potential ReferenceErrors when referencing the global google object if the external library fails to load. Implementing the suggested safety checks and fallback values will ensure robust execution.

Comment thread assets/js/sign-in-with-google.ts
Comment thread assets/js/sign-in-with-google.ts Outdated
Comment on lines +98 to +102
google?.accounts.id.initialize( {
client_id: data.clientID, // eslint-disable-line camelcase
callback: ( response ) => handleCredentialResponse( response, data ),
library_name: 'Site-Kit', // eslint-disable-line camelcase
} as google.accounts.id.IdConfiguration ); // eslint-disable-line sitekit/acronym-case

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The google global variable is loaded asynchronously via an external script. If the script fails to load or is blocked by privacy extensions/ad-blockers, referencing google directly will throw a ReferenceError at runtime, even with optional chaining (google?.). To prevent script crashes, wrap the initialization in a check to ensure google is defined and has the expected properties.

	if ( typeof google !== 'undefined' && google?.accounts?.id ) {
		google.accounts.id.initialize( {
			client_id: data.clientID, // eslint-disable-line camelcase
			callback: ( response ) => handleCredentialResponse( response, data ),
			library_name: 'Site-Kit', // eslint-disable-line camelcase
		} as google.accounts.id.IdConfiguration ); // eslint-disable-line sitekit/acronym-case
	}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Updated to check existence of google.accounts.id early and return if it doesn't exist.

Comment on lines +144 to +146
if ( data.shouldShowOneTapPrompt ) {
google?.accounts.id.prompt();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Ensure that the google global variable is defined before calling prompt() to avoid a ReferenceError if the external script fails to load.

Suggested change
if ( data.shouldShowOneTapPrompt ) {
google?.accounts.id.prompt();
}
if ( data.shouldShowOneTapPrompt && typeof google !== 'undefined' && google?.accounts?.id ) {
google.accounts.id.prompt();
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Updated to check existence of google.accounts.id early and return if it doesn't exist.

@JakePT JakePT marked this pull request as ready for review June 17, 2026 13:17

@aaemnnosttv aaemnnosttv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks @JakePT – this is off to a good start. I have a few suggestions for how we can improve this a bit more structurally which should also simplify a bit of the implementation / testing too.


if ( data.isExistingUserFlow ) {
body.append( 'integration', 'existing_user' );
body.append( 'connectNonce', data.connectNonce );

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This may have been renamed unintentionally but the server side expects this to be using the name as before (which shouldn't change here). See

// Tie the link request to the current WordPress session so the
// user's auth cookie alone can't validate a cross-site request.
$nonce = $input->filter( INPUT_POST, 'connect_nonce' );

Suggested change
body.append( 'connectNonce', data.connectNonce );
body.append( 'connect_nonce', data.connectNonce );

Comment on lines +185 to +189
const data = window._googlesitekitSignInWithGoogleData;

if ( data ) {
setupSignInWithGoogle( data );
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is mostly what I had in mind for this entry point – you can see an example of this in assets/js/analytics-advanced-tracking.ts which is worth aligning a bit more closely with.

One important difference is the use of the global variable which we don't really need. We've solved this differently more recently in our inline Pointers JS which uses data attributes (set by the WP inline script function) to pass data to the script without needing to write JS dynamically with PHP.

Aside from that, the general ideas are to keep the function a bit more pure and typed, so that we can handle things like the availability of google.accounts.id before we call it, and the types and tests+mocks become simpler as a result. The function itself only actually needs google.accounts.id so we can pass that in when called, and only call it when it's there.

Elaborated details 🤖

Structure suggestion — lean further on the analytics-advanced-tracking pattern (non-blocking)

This looks modeled on analytics-advanced-tracking already (same window._googlesitekit… global), which is a good base — but that sibling predates both TS and the idea of passing config via script-tag attributes, so I think we can take it a step further:

Split it like the sibling does:

assets/js/sign-in-with-google.ts                            ← thin entrypoint/bootstrap
assets/js/sign-in-with-google/set-up-sign-in-with-google.ts ← setUpSignInWithGoogle + handleCredentialResponse
assets/js/sign-in-with-google/set-up-sign-in-with-google.test.ts
assets/js/sign-in-with-google/types.ts                      ← SignInWithGoogleData

Drop the global, carry config on the script tag instead. Hang it off the bundle's own <script> tag as a data- attribute and read it back with document.currentScript.dataset (this is what Pointers::print_pointer_script() does). Bonus: wp_print_script_tag() escapes attribute values for us automatically. Whether the config travels as one JSON blob or separate data- attributes is your call — both get the escaping. (If you split it: dataset returns everything as strings, so the booleans and the TTL number need converting and defaultButtonOptions becomes its own JSON attribute anyway; a single blob avoids that. Fine either way.) Once the global's gone, the SignInWithGoogleData import in globals.d.ts drops out too.

Have the entrypoint do the messy parts, and pass dependencies into the function. The sibling already does this — it hands sendEvent into setUpAdvancedTracking rather than reaching for window.gtag. Same idea here:

  • Entrypoint: read config off dataset, check google?.accounts?.id is actually loaded, then call setUpSignInWithGoogle( google.accounts.id, config ).
  • Function: takes the config and google.accounts.id as arguments, never touches a global. Because it receives google.accounts.id already verified, the internal typeof google === 'undefined' guard goes away — which is also the cleanest answer to the Gemini note about checking things are defined before calling methods (it happens once, up front).

Why it's worth it: the test currently has to do ( window.google as unknown as { … } ) = { … } to fake the global. With the function taking plain arguments, the test just passes a config object and a few jest.fn()s — no global juggling, no await import(). So this mostly removes existing awkwardness rather than adding ceremony.

Two notes to keep it from sprawling: keep handleCredentialResponse in the same file as setUpSignInWithGoogle (only used there — the useful seam is entrypoint vs. logic, not function vs. function), and the only firm asks are (1) the function takes a typed config object and (2) it takes google.accounts.id as an argument with the load check moved to the bootstrap.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants