Skip to content

Move inline JS to external script files#814

Open
aslamdoctor wants to merge 3 commits intoWordPress:masterfrom
aslamdoctor:812-move-inline-js-to-external-scripts
Open

Move inline JS to external script files#814
aslamdoctor wants to merge 3 commits intoWordPress:masterfrom
aslamdoctor:812-move-inline-js-to-external-scripts

Conversation

@aslamdoctor
Copy link
Contributor

Summary

  • Extract 6 inline <script> blocks from 4 PHP files into 5 new external JS files under providers/js/, improving CSP compatibility and enabling proper WordPress script dependency management (fixes issues like Missing script dependency: Uncaught TypeError: wp.apiRequest is not a function #558 where wp.apiRequest was unavailable)
  • New files: totp-admin-qrcode.js, totp-admin.js, backup-codes-admin.js, two-factor-login.js, two-factor-login-authcode.js
  • Convert customizer messenger one-liner to wp_add_inline_script() instead of raw <script> tag
  • All new JS files pass npm run lint:js (WordPress ES5 rules)

Test plan

  • Admin profile — TOTP setup: QR code renders, setup form submits, checkbox focuses input, invalid code shows error
  • Admin profile — TOTP reset: "Reset authenticator app" button works, re-renders setup form with QR code
  • Admin profile — Backup codes: Generate, copy, download all work; code count updates
  • Login page — TOTP: Authcode field focused, numeric-only enforcement, auto-submit at expected length
  • Login page — Email: Authcode field cleared + focused, numeric enforcement, auto-submit
  • Login page — Backup codes: Focus + numeric enforcement
  • Network tab: External JS files load with correct dependencies (jquery, wp-api-request, qrcode.js)
  • Browser console: No JS errors on any page
  • Customizer interim login: Messenger script fires correctly

Fixes #812

Extract 6 inline script blocks from 4 PHP files into 5 new external JS
files under providers/js/, improving CSP compatibility and enabling
proper WordPress script dependency management.

New files:
- totp-admin-qrcode.js: QR code generation for TOTP setup
- totp-admin.js: TOTP setup form, checkbox focus, reset key
- backup-codes-admin.js: Backup code generation, copy, download
- two-factor-login.js: Shared login page authcode clear/focus
- two-factor-login-authcode.js: Numeric-only enforcement, auto-submit

The customizer messenger one-liner uses wp_add_inline_script() instead
of a raw <script> tag.

Fixes WordPress#812
@aslamdoctor aslamdoctor marked this pull request as ready for review February 25, 2026 01:55
@github-actions
Copy link

github-actions bot commented Feb 25, 2026

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: aslamdoctor <aslamdoctor@git.wordpress.org>
Co-authored-by: masteradhoc <masteradhoc@git.wordpress.org>
Co-authored-by: georgestephanis <georgestephanis@git.wordpress.org>

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

The "Reset authenticator app" button's AJAX response injects HTML with a
QR code placeholder, but the QR code generator script only runs on page
load and never re-triggers. Additionally, the qrcode library wasn't
loaded when TOTP was already configured.

Add qr-code-generator as a dependency of totp-admin script and generate
the QR code in JavaScript after the reset response HTML is inserted.
Move all var declarations to the top of their respective function scopes.
Copy link
Collaborator

@masteradhoc masteradhoc left a comment

Choose a reason for hiding this comment

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

Thanks for the fast fix regarding my comment about the QR-Code not rendering after resetting the TOTP. Checked it now and looks good from my side.

@georgestephanis
Copy link
Collaborator

For big migrations like this, I've found that copilot is really good at catching stuff my eyes gloss over. Just wanna make sure it doesn't surface anything significant.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR extracts previously inline JavaScript from provider PHP templates into external script files under providers/js/, enabling better CSP compatibility and allowing WordPress dependency management (notably ensuring wp-api-request is properly enqueued when needed).

Changes:

  • Added 5 new external JS files for login behaviors and provider admin UIs (TOTP + backup codes).
  • Updated provider PHP to register/enqueue these scripts and pass dynamic values via wp_localize_script().
  • Replaced a raw customizer <script> snippet with wp_add_inline_script().

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
providers/js/two-factor-login.js New login helper script (focus/clear authcode).
providers/js/two-factor-login-authcode.js New login authcode input enforcement + autosubmit logic.
providers/js/totp-admin.js New admin/profile TOTP setup/reset interactions via REST calls.
providers/js/totp-admin-qrcode.js New TOTP QR-code rendering script using qrcode generator.
providers/js/backup-codes-admin.js New backup codes UI (generate/copy/download) via REST calls.
providers/class-two-factor-totp.php Registers/enqueues new TOTP scripts and localizes REST params.
providers/class-two-factor-email.php Enqueues shared login script instead of inline <script>.
providers/class-two-factor-backup-codes.php Registers/enqueues new backup codes script and localizes REST params.
class-two-factor-core.php Registers login scripts and switches customizer snippet to wp_add_inline_script().

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +54 to +55
*/
public function enqueue_assets() {
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

admin_enqueue_scripts passes the $hook_suffix argument, but enqueue_assets() is declared with no parameters. On PHP 8+, WordPress will call this with 1 arg and trigger an ArgumentCountError, breaking admin pages. Make enqueue_assets( $hook_suffix = '' ) accept the parameter (or register the action with accepted_args = 0) so the callback signature matches the hook.

Suggested change
*/
public function enqueue_assets() {
*
* @param string $hook_suffix Optional. The current admin page hook suffix.
*/
public function enqueue_assets( $hook_suffix = '' ) {

Copilot uses AI. Check for mistakes.
Comment on lines +26 to +28
if ( undefined !== form.requestSubmit ) {
form.requestSubmit();
form.submit.disabled = 'disabled';
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

form.submit.disabled = 'disabled' won't reliably disable the submit button (most login forms don't have a control named submit, and form.submit is typically the form method). This means auto-submit can still be triggered multiple times. Disable the actual submit control (e.g., #wp-submit or form.querySelector('[type="submit"]')) and guard form being null before calling requestSubmit().

Suggested change
if ( undefined !== form.requestSubmit ) {
form.requestSubmit();
form.submit.disabled = 'disabled';
if ( form && typeof form.requestSubmit === 'function' ) {
form.requestSubmit();
var submitControl = form.querySelector( '[type="submit"]' );
if ( submitControl ) {
submitControl.disabled = true;
}

Copilot uses AI. Check for mistakes.
inputEl.addEventListener(
'input',
function() {
var value = this.value.replace( /[^0-9 ]/g, '' ).trimStart();
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

trimStart() is not available in ES5 and can break in environments without that newer String API. Since these scripts are intended to follow WordPress' ES5 rules, avoid trimStart() here (e.g., strip leading whitespace via a regex) to keep compatibility consistent.

Suggested change
var value = this.value.replace( /[^0-9 ]/g, '' ).trimStart();
var value = this.value.replace( /[^0-9 ]/g, '' ).replace( /^\s+/, '' );

Copilot uses AI. Check for mistakes.
enable_provider: true
}
} ).fail( function( response, status ) {
var errorMessage = response.responseJSON.message || status,
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

In the fail handler, response.responseJSON can be undefined (e.g., non-JSON error responses), which would throw when accessing .message and prevent the error UI from rendering. Guard the lookup (check response.responseJSON && response.responseJSON.message) and fall back to a safer string (like response.statusText or status).

Suggested change
var errorMessage = response.responseJSON.message || status,
var errorMessage = ( response && response.responseJSON && response.responseJSON.message ) || ( response && response.statusText ) || status || '',

Copilot uses AI. Check for mistakes.
Comment on lines +22 to +24
svg.role = 'image';
svg.ariaLabel = 'Authenticator App QR Code';
title.innerText = svg.ariaLabel;
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

The SVG accessibility attributes are set using svg.role = 'image' and svg.ariaLabel = .... image is not a valid ARIA role value (use img), and setting ARIA via DOM properties is not consistently supported for SVG; prefer setAttribute('role', 'img') and setAttribute('aria-label', ...). Also, the label is hard-coded in English here; pass a localized label via twoFactorTotpAdmin (as done in twoFactorTotpQrcode) so it can be translated.

Suggested change
svg.role = 'image';
svg.ariaLabel = 'Authenticator App QR Code';
title.innerText = svg.ariaLabel;
var ariaLabel = ( typeof twoFactorTotpAdmin !== 'undefined' && twoFactorTotpAdmin && twoFactorTotpAdmin.qrCodeAriaLabel ) ? twoFactorTotpAdmin.qrCodeAriaLabel : 'Authenticator App QR Code';
svg.setAttribute( 'role', 'img' );
svg.setAttribute( 'aria-label', ariaLabel );
title.innerText = ariaLabel;

Copilot uses AI. Check for mistakes.
Comment on lines +23 to +25
svg.role = 'image';
svg.ariaLabel = twoFactorTotpQrcode.qrCodeLabel;
title.innerText = svg.ariaLabel;
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

The SVG is annotated via svg.role = 'image' and svg.ariaLabel = .... image is not a valid ARIA role value (use img), and ARIA should be applied via attributes for SVG (setAttribute('role', 'img'), setAttribute('aria-label', ...)) for consistent accessibility support.

Suggested change
svg.role = 'image';
svg.ariaLabel = twoFactorTotpQrcode.qrCodeLabel;
title.innerText = svg.ariaLabel;
svg.setAttribute( 'role', 'img' );
svg.setAttribute( 'aria-label', twoFactorTotpQrcode.qrCodeLabel );
title.innerText = twoFactorTotpQrcode.qrCodeLabel;

Copilot uses AI. Check for mistakes.
Comment on lines +1589 to +1594
<?php
wp_add_inline_script(
'customize-base',
'setTimeout( function(){ new wp.customize.Messenger({ url: ' . wp_json_encode( esc_url( wp_customize_url() ) ) . ', channel: \'login\' }).send(\'login\') }, 1000 );'
);
?>
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

wp_add_inline_script() is called after do_action( 'login_footer' ). On this flow, login_footer is where core prints footer scripts (including customize-base), so adding the inline script afterwards means it likely never gets output and the messenger won't fire. Move the wp_add_inline_script( 'customize-base', ... ) call to before do_action( 'login_footer' ) (after wp_enqueue_script( 'customize-base' )) so it prints with the customize-base script tag.

Copilot uses AI. Check for mistakes.
@georgestephanis
Copy link
Collaborator

Also previously:

#562

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.

Move inline JS to external scripts

4 participants