39    private const ALLOWED_REFRESH_INTERVAL = 30 * 60; 
 
   46    private int $lastFetch = 0;
 
   48    public function __construct(
 
   49        private readonly \
Logger $logger,
 
   51        private readonly 
int $keyRefreshIntervalSeconds = self::ALLOWED_REFRESH_INTERVAL
 
   65            $this->keyring === 
null || 
 
   66            ($this->keyring->getKey($keyId) === 
null && $this->lastFetch < time() - $this->keyRefreshIntervalSeconds) 
 
   69            $this->fetchKeys()->onCompletion(
 
   70                onSuccess: fn(
AuthKeyring $newKeyring) => $this->resolveKey($resolver, $newKeyring, $keyId),
 
   71                onFailure: $resolver->reject(...)
 
   74            $this->resolveKey($resolver, $this->keyring, $keyId);
 
 
   83    private function resolveKey(
PromiseResolver $resolver, AuthKeyring $keyring, 
string $keyId) : void{
 
   84        $key = $keyring->getKey($keyId);
 
   86            $this->logger->debug(
"Key $keyId not recognised!");
 
   91        $this->logger->debug(
"Key $keyId found in keychain");
 
   92        $resolver->
resolve([$keyring->getIssuer(), $key]);
 
   99    private function onKeysFetched(?array $keys, 
string $issuer, ?array $errors) : void{
 
  100        $resolver = $this->resolver;
 
  101        if($resolver === 
null){
 
  102            throw new AssumptionFailedError(
"Not expecting this to be called without a resolver present");
 
  104        if($errors !== 
null){
 
  105            $this->logger->error(
"The following errors occurred while fetching new keys:\n\t- " . implode(
"\n\t-", $errors));
 
  110            $this->logger->critical(
"Failed to fetch authentication keys from Mojang's API. Xbox players may not be able to authenticate!");
 
  114            foreach($keys as $keyModel){
 
  115                if($keyModel->use !== 
"sig" || $keyModel->kty !== 
"RSA"){
 
  116                    $this->logger->error(
"Key ID $keyModel->kid doesn't have the expected properties: expected use=sig, kty=RSA, got use=$keyModel->use, kty=$keyModel->kty");
 
  119                $derKey = JwtUtils::rsaPublicKeyModExpToDer($keyModel->n, $keyModel->e);
 
  123                    JwtUtils::parseDerPublicKey($derKey);
 
  124                }
catch(JwtException $e){
 
  125                    $this->logger->error(
"Failed to parse RSA public key for key ID $keyModel->kid: " . $e->getMessage());
 
  126                    $this->logger->logException($e);
 
  131                $pemKeys[$keyModel->kid] = $derKey;
 
  134            if(count($keys) === 0){
 
  135                $this->logger->critical(
"No valid authentication keys returned by Mojang's API. Xbox players may not be able to authenticate!");
 
  138                $this->logger->info(
"Successfully fetched " . count($keys) . 
" new authentication keys from issuer $issuer, key IDs: " . implode(
", ", array_keys($pemKeys)));
 
  139                $this->keyring = 
new AuthKeyring($issuer, $pemKeys);
 
  140                $this->lastFetch = time();
 
  141                $resolver->
resolve($this->keyring);
 
  149    private function fetchKeys() : Promise{
 
  150        if($this->resolver !== null){
 
  151            $this->logger->debug(
"Key refresh was requested, but it's already in progress");
 
  152            return $this->resolver->getPromise();
 
  155        $this->logger->notice(
"Fetching new authentication keys");
 
  158        $resolver = 
new PromiseResolver();
 
  159        $this->resolver = $resolver;
 
  161        $this->asyncPool->submitTask(
new FetchAuthKeysTask($this->onKeysFetched(...)));