PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.3.4
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.3.4
1.3.5 1.3.4 1.3.3 1.3.2 1.3.1 1.3.0 1.2.4 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 All 31 releases
← All changes | includes/modules/Mcp/Mcp_Hub.php +53 -12 1.2.4 → 1.3.4 View file →
@@ -195,9 +195,9 @@
195 195 return array(
196 196 'attached' => $state['attached'],
197 197 'account_email' => $state['account_email'],
198 198 'attached_at' => $state['attached_at'],
199 - 'site_url' => home_url( '/' ),
199 + 'site_url' => Mcp_Pairing::absolute( home_url( '/' ) ),
200 200 // Method 1 paste-in credential — the existing per-site token.
201 201 // Empty until generate_token() (or a per-site Connect) mints one.
202 202 'site_token' => Mcp_Pairing::site_token(),
203 203 'hub_url' => self::hub_url(),
@@ -624,12 +624,48 @@
624 624 *
625 625 * @return array<string,mixed>|\WP_Error
626 626 */
627 627 public static function gtmetrix_test() {
628 - return self::gtmetrix_request( 'POST', '/api/site/gtmetrix/test' );
628 + return self::hub_request( 'POST', '/api/site/gtmetrix/test' );
629 629 }
630 630
631 631 /**
632 + * Ask the Hub to run a PageSpeed Insights audit for this site.
633 + *
634 + * The PSI twin of gtmetrix_test(): the Hub holds a real Google API key, so
635 + * routing the audit through it is what makes a keyless site's test work —
636 + * an unkeyed call straight to Google shares one anonymous per-IP pool with
637 + * every other unkeyed caller and refuses with "Quota exceeded" under any
638 + * real load (issue #426).
639 + *
640 + * The Hub answers 202 with a run row and audits in the background; the
641 + * result arrives via psi_runs().
642 + *
643 + * @param string $strategy 'mobile', 'desktop' or 'both'.
644 + * @return array<string,mixed>|\WP_Error
645 + */
646 + public static function psi_test( string $strategy = 'mobile' ) {
647 + $strategy = in_array( $strategy, array( 'mobile', 'desktop', 'both' ), true ) ? $strategy : 'mobile';
648 + return self::hub_request( 'POST', '/api/site/psi/test', array( 'strategy' => $strategy ) );
649 + }
650 +
651 + /**
652 + * PSI runs for this site, finished ones copied into the local history.
653 + *
654 + * The polling half of psi_test() — that route answers before the audit
655 + * runs, so without this the plugin would never learn the score.
656 + *
657 + * @return array<string,mixed>|\WP_Error
658 + */
659 + public static function psi_runs() {
660 + $result = self::hub_request( 'GET', '/api/site/psi/runs' );
661 + if ( ! is_wp_error( $result ) ) {
662 + self::store_hub_results( $result );
663 + }
664 + return $result;
665 + }
666 +
667 + /**
632 668 * Recent Hub-run tests for this site, plus the remaining allowance.
633 669 *
634 670 * Polled while a run is in flight, and read once on load so the button can
635 671 * show the count before anyone presses anything.
@@ -636,9 +672,9 @@
636 672 *
637 673 * @return array<string,mixed>|\WP_Error
638 674 */
639 675 public static function gtmetrix_runs() {
640 - $result = self::gtmetrix_request( 'GET', '/api/site/gtmetrix/runs' );
676 + $result = self::hub_request( 'GET', '/api/site/gtmetrix/runs' );
641 677 if ( ! is_wp_error( $result ) ) {
642 678 self::store_hub_results( $result );
643 679 }
644 680 return $result;
@@ -682,15 +718,19 @@
682 718 if ( $ts <= 0 || '' === $remote_id || Score_Store::exists_remote( $remote_id ) ) {
683 719 continue;
684 720 }
685 721
722 + // The runs table is shared between providers on the Hub too — a
723 + // PSI run must not be recorded as a GTmetrix row.
724 + $provider = 'psi' === ( $run['provider'] ?? '' ) ? 'psi' : 'gtmetrix';
725 +
686 726 Score_Store::insert(
687 727 array(
688 728 'ok' => true,
689 - 'provider' => 'gtmetrix',
729 + 'provider' => $provider,
690 730 'ts' => $ts,
691 731 'url' => (string) ( $r['url'] ?? '' ),
692 - 'strategy' => (string) ( $r['strategy'] ?? 'desktop' ),
732 + 'strategy' => (string) ( $r['strategy'] ?? ( 'psi' === $provider ? 'mobile' : 'desktop' ) ),
693 733 'score' => $r['score'] ?? null,
694 734 'metrics' => array(
695 735 'lcp' => $r['lcp'] ?? null,
696 736 'fcp' => $r['fcp'] ?? null,
@@ -717,25 +757,26 @@
717 757 * failure into a stable error code — must behave identically for both. A
718 758 * divergence there would show up as the UI handling a quota error on one
719 759 * path and not the other.
720 760 *
721 - * @param string $method HTTP method.
722 - * @param string $path Path under the hub base URL.
761 + * @param string $method HTTP method.
762 + * @param string $path Path under the hub base URL.
763 + * @param array<string,mixed> $body Extra POST body fields beside site_url.
723 764 * @return array<string,mixed>|\WP_Error
724 765 */
725 - private static function gtmetrix_request( string $method, string $path ) {
766 + private static function hub_request( string $method, string $path, array $body = array() ) {
726 767 $token = Mcp_Pairing::site_token();
727 768 if ( '' === $token ) {
728 769 return new \WP_Error(
729 770 'not_connected',
730 - __( 'Connect this site to xSpeed Hub to run a free GTmetrix test.', 'xspeed' )
771 + __( 'Connect this site to xSpeed Hub to run a free speed test.', 'xspeed' )
731 772 );
732 773 }
733 774
734 775 $site_url = self::site_url_canonical();
735 776 $args = array(
736 - // A GTmetrix test takes a minute, but the Hub answers as soon as it
737 - // has ACCEPTED the job — this waits for that handshake only.
777 + // A test takes a minute, but the Hub answers as soon as it has
778 + // ACCEPTED the job — this waits for that handshake only.
738 779 'timeout' => 15,
739 780 'headers' => array( 'X-XSpeed-Site-Token' => $token ),
740 781 );
741 782
@@ -740,9 +781,9 @@
740 781 );
741 782
742 783 if ( 'POST' === $method ) {
743 784 $args['headers']['Content-Type'] = 'application/json';
744 - $args['body'] = wp_json_encode( array( 'site_url' => $site_url ) );
785 + $args['body'] = wp_json_encode( array_merge( array( 'site_url' => $site_url ), $body ) );
745 786 $resp = wp_remote_post( self::hub_url() . $path, $args );
746 787 } else {
747 788 $resp = wp_remote_get(
748 789 add_query_arg( array( 'site_url' => rawurlencode( $site_url ) ), self::hub_url() . $path ),