\WP_REST_Server::CREATABLE, 'callback' => array( $this, 'save_token' ), 'permission_callback' => array( $this, 'can_manage' ), 'args' => array( 'token' => self::arg( array( 'type' => 'string' ) ), ), ), array( 'methods' => \WP_REST_Server::DELETABLE, 'callback' => array( $this, 'delete_token' ), 'permission_callback' => array( $this, 'can_manage' ), 'args' => array(), ), ) ); register_rest_route( self::REST_NAMESPACE, '/clarity/sync', array( 'methods' => \WP_REST_Server::CREATABLE, 'callback' => array( $this, 'sync' ), 'permission_callback' => array( $this, 'can_manage' ), 'args' => array(), ) ); register_rest_route( self::REST_NAMESPACE, '/clarity/insights', array( 'methods' => \WP_REST_Server::READABLE, 'callback' => array( $this, 'insights' ), 'permission_callback' => array( $this, 'can_view' ), 'args' => self::range_args( false ), ) ); } /** * POST /clarity/token {token} → {clarity}. * * The token must be JWT-shaped (400 clarity_token_invalid otherwise). * The table is created and the first sync runs before the token is * saved; a token Clarity rejects is not saved (clarity_unauthorized). * Other first-sync failures keep the token: the response is then 200, * with the failure in clarity.status.error. * * @param \WP_REST_Request $request Request. * @return \WP_REST_Response|\WP_Error */ public function save_token( \WP_REST_Request $request ) { $token = $request->get_param( 'token' ); try { $this->plugin->clarity()->save_token( is_string( $token ) ? $token : '' ); } catch ( ClarityError $e ) { return $e->to_wp_error(); } return $this->summary(); } /** * DELETE /clarity/token → {clarity}: removes the token and the status * and stops the cron; stored snapshots are kept. */ public function delete_token(): \WP_REST_Response { $this->plugin->clarity()->delete_token(); return $this->summary(); } /** * POST /clarity/sync → {clarity}, or 429 clarity_too_soon (an attempt * in the last hour), 429 clarity_budget_exhausted (fewer than 2 calls * left today), or the sync's own error. * * @return \WP_REST_Response|\WP_Error */ public function sync() { try { $this->plugin->clarity()->sync_now(); } catch ( ClarityError $e ) { return $e->to_wp_error(); } return $this->summary(); } /** * GET /clarity/insights?range=&start=&end= → {site, trend, pages, meta}. * Pages carry their title and GA4 views (null without Google, or when * GA4 has none recorded for the page); the Clarity data never needs a * Google connection. * * @param \WP_REST_Request $request Request. */ public function insights( \WP_REST_Request $request ): \WP_REST_Response { $insights = new Insights( $this->plugin->clarity(), $this->plugin->clarity_store(), $this->plugin->reports() ); return new \WP_REST_Response( $insights->for_range( self::range_from( $request, false ) ) ); } /** * The {clarity} response: the same object as GET /settings.clarity. */ private function summary(): \WP_REST_Response { return new \WP_REST_Response( array( 'clarity' => $this->plugin->clarity()->summary() ) ); } }