$resource, 'redirect_uri' => $redirect_uri, 'scope' => $scope, 'nonce' => $state, 'code_challenge' => $challenge, 'code_challenge_method' => 'S256', ]); return self::base() . self::START_PATH . '?' . $query; } /** * Exchanges a forwarded authorization code for a token set, presenting * the PKCE verifier. The relay adds the client secret. * * @param string $code the authorization code * @param string $resource the site's resource key * @param null|string $code_verifier the PKCE verifier (S256 pre-image) * @return array decoded token array on success, or ['error' => slug] */ public static function exchange($code, $resource, $code_verifier) { return self::post(self::EXCHANGE_PATH, [ 'code' => $code, 'resource' => $resource, 'codeVerifier' => $code_verifier, ]); } /** * Mints a fresh access token from a stored refresh token. Google's * refresh grant does not return a new refresh token; the caller keeps * the existing one. * * @param string $refresh_token the stored refresh token * @param string $resource the site's resource key * @return array decoded token array on success, or ['error' => slug] */ public static function refresh($refresh_token, $resource) { return self::post(self::REFRESH_PATH, [ 'refreshToken' => $refresh_token, 'resource' => $resource, ]); } /** * POSTs a JSON body to a relay endpoint and decodes the JSON response. * Any transport error, non-2xx status, or malformed body collapses to * an ['error' => ...] array so callers can treat failures uniformly * (mirroring the Google client's error-array convention). Tokens are * never logged here. * * @param string $path the endpoint path (one of the *_PATH constants) * @param array $body the request payload * @return array */ protected static function post($path, array $body) { $response = wp_remote_post(self::base() . $path, [ 'headers' => ['Content-Type' => 'application/json'], 'body' => wp_json_encode($body), 'timeout' => 15, ]); if (is_wp_error($response)) { return ['error' => $response->get_error_message()]; } $status = (int) wp_remote_retrieve_response_code($response); $data = json_decode((string) wp_remote_retrieve_body($response), true); if ($status < 200 || $status >= 300) { return ['error' => is_array($data) && isset($data['error']) ? (string) $data['error'] : ('http_' . $status)]; } if (!is_array($data) || !isset($data['access_token'])) { return ['error' => 'invalid_relay_response']; } return $data; } }