<?php
declare(strict_types=1);

namespace app\components\behaviors;

use Yii;
use yii\base\Behavior;
use yii\base\InvalidArgumentException as YiiBaseInvalidArgumentException;
use yii\web\Controller;
use yii\web\View;

use app\components\SessionManager;
use app\components\interfaces\SessionInterface;
use app\components\traits\CacheJsCssTrait;
use app\helpers\LogHelper;
use app\helpers\JwtHelper;
use app\helpers\ParamHelper;
use app\helpers\TimeHelper;

class FormInteractionBehavior extends Behavior
{
	private bool $_debugMode = false;

	public int $minElapsedTimeMs = 2000;
	public string $sessionKey = '__form_interaction_nonce';
	
	// @var User $userIp and $userAgent
	public ?string $userIp = null;
	public ?string $userAgent = null;
	
	public function init(): void
	{
		/**
		* Call to parent constructor
		*
		* Always call the parent init method first to ensure any
		* initialization logic defined in the base class is executed first.
		*/
		parent::init();
		
		// ... initialization after configuration is applied...
		
		if ($this->__sessionManager === null)
		{
			$this->__sessionManager = new SessionManager();
		}
		
		if (($this->userIp === null) xor ($this->userAgent === null))
		{
			throw new YiiBaseInvalidArgumentException('Both `userIp` and `userAgent` property must be set.');
		}
	}
	
	public function events(): array
	{
		return [
			Controller::EVENT_BEFORE_ACTION => 'generateNonce',
		];
	}
	
	public function generateNonce(): void
	{
		if (!$this->getSession()->has($this->sessionKey))
		{
			$this->getSession()->set($this->sessionKey, bin2hex(random_bytes(16)));
		}
	}
	
	public function generateInteractionToken(): string
	{
		$nonce = $this->getSession()->get($this->sessionKey);
		
		if ($nonce === null)
		{
			if ($this->_debugMode)
			{
				LogHelper::debug("Nonce is null at token generation time", ['method' => __METHOD__]);
			}
			
			/**
			* Optionally regenerate here or throw an exception
			*
			* Note: If the code is setting the token, this is not ideal
			* and counters the entire point.
			*
			$nonce = bin2hex(random_bytes(16));
			$this->getSession()->set($this->sessionKey, $nonce);//*/
		}
		
		$issuedAt = time();
		
		return JwtHelper::generateInteractionToken($nonce, $issuedAt);
	}
	
	public function validateInteractionToken(?string $token): bool
	{
		if (empty($token))
		{
			$errorId = uniqid('form_interaction_behavior_detected_', true);
			LogHelper::logBotAction(
				"Bot suspicion: empty interaction token. Error ID: {$errorId}",
				[
					'ip' => $this->userIp,
					'userAgent' => $this->userAgent,
					'errorId' => $errorId,
					'method' => __METHOD__
				]
			);
			
			return false;
		}
		
		$decoded = JwtHelper::validateToken($token);
		
		if ($decoded === false)
		{
			$errorId = uniqid('form_interaction_behavior_detected_', true);
			LogHelper::logBotAction(
				"Bot suspicion: invalid JWT token. Error ID: {$errorId}",
				[
					'ip' => $this->userIp,
					'userAgent' => $this->userAgent,
					'errorId' => $errorId,
					'method' => __METHOD__
				]
			);
		
			return false;
		}
		
		// Validate nonce
		$sessionNonce = $this->getSession()->get($this->sessionKey);
		
		if (empty($decoded->nonce) || $decoded->nonce !== $sessionNonce)
		{
			$errorId = uniqid('form_interaction_behavior_detected_', true);
			LogHelper::logBotAction(
				"Bot suspicion: nonce mismatch or missing. Error ID: {$errorId}",
				[
					'ip' => $this->userIp,
					'userAgent' => $this->userAgent,
					'errorId' => $errorId,
					'method' => __METHOD__
				]
			);
		
			return false;
		}
		
		// Validate timestamp and elapsed time
		if (empty($decoded->iat) || !is_numeric($decoded->iat))
		{
			$errorId = uniqid('form_interaction_behavior_detected_', true);
			LogHelper::logBotAction(
				"Bot suspicion: invalid issued-at timestamp. Error ID: {$errorId}",
				[
					'ip' => $this->userIp,
					'userAgent' => $this->userAgent,
					'errorId' => $errorId,
					'method' => __METHOD__
				]
			);
			
			return false;
		}
		
		$elapsedMs = (microtime(true) * 1000) - ((int) $decoded->iat * 1000);
		
		if ($elapsedMs < $this->minElapsedTimeMs)
		{
			$errorId = uniqid('form_interaction_behavior_detected_', true);
			LogHelper::logBotAction(
				"Bot suspicion: elapsed time too short ({$elapsedMs} ms). Error ID: {$errorId}",
				[
					'ip' => $this->userIp,
					'userAgent' => $this->userAgent,
					'elapsed time' => $elapsedMs,
					'errorId' => $errorId,
					'method' => __METHOD__
				]
			);
		
			return false;
		}
		
		// Passed - remove nonce to prevent replay attacks
		$this->getSession()->remove($this->sessionKey);
		
		return true;
	}
	
	/**
	* Shortcut getter for session
	*/
	protected function getSession(): SessionInterface
	{
		return $this->__sessionManager;
	}
}
```
the js code
```
		return <<<JS
			(function(\$)
			{
				window.FormInteractionJwt = {token: '{$token}'};
				
				\$(function()
				{
					var interacted = false;
					
					function onInteraction()
					{
						if (interacted)
						{
							return;
						}
						
						interacted = true;
						
						\$('form').each(function()
						{
							var \$form = \$(this);
							var \$input = \$form.find('input[name="user_interaction_token"]');
							
							if (\$input.length === 0)
							{
								\$input = \$('<input>',
								{
									type: 'hidden',
									name: 'user_interaction_token'
								});
								\$form.append(\$input);
							}
							
							\$input.val(window.FormInteractionJwt.token);
						});
					}
					
					/* Listen for the first user interaction event */
					\$(window).one('scroll mousemove keydown click touchstart touchmove touchend', onInteraction);
					
					/* Fallback: inject token after a random delay between 1-8 seconds */
					var fallbackDelay = (Math.floor(Math.random() * 8) + 1) * 1000;
					setTimeout(onInteraction, fallbackDelay);
				});
			})(jQuery);
			JS;
```
The issue is:

TypeError
app\helpers\JwtHelper::generateInteractionToken(): Argument #1 ($nonce) must be of type string, null given, called in /app/components/behaviors/FormInteractionBehavior.php on line 135

but if I set it:


	public function generateInteractionToken(): string
	{
		$nonce = $this->getSession()->get($this->sessionKey);
		
		if ($nonce === null)
		{
			if ($this->_debugMode)
			{
				LogHelper::debug("Nonce is null at token generation time", ['method' => __METHOD__]);
			}
			
			/**
			* Optionally regenerate here or throw an exception
			*
			* Note: If the code is setting the token, this is not ideal
			* and counters the entire point.
			*/
			$nonce = bin2hex(random_bytes(16));
			$this->getSession()->set($this->sessionKey, $nonce);//*/
		}
		
		$issuedAt = time();
		
		return JwtHelper::generateInteractionToken($nonce, $issuedAt);
	}

Does this not defeat the entire point of this? I believe it does, its setting a token that is null for a reason, the user has not interacted with the form and
is possibly a bot.