40    private const KEYS_ON_COMPLETION = 
"completion";
 
   42    private const MINECRAFT_SERVICES_DISCOVERY_URL = 
"https://client.discovery.minecraft-services.net/api/v1.0/discovery/MinecraftPE/builds/" . ProtocolInfo::MINECRAFT_VERSION_NETWORK;
 
   43    private const AUTHORIZATION_SERVICE_URI_FALLBACK = 
"https://authorization.franchise.minecraft-services.net";
 
   44    private const AUTHORIZATION_SERVICE_OPENID_CONFIGURATION_PATH = 
"/.well-known/openid-configuration";
 
   45    private const AUTHORIZATION_SERVICE_KEYS_PATH = 
"/.well-known/keys";
 
   49    private string $issuer;
 
   58        \Closure $onCompletion
 
   60        $this->
storeLocal(self::KEYS_ON_COMPLETION, $onCompletion);
 
 
   68            $authServiceUri = $this->getAuthServiceURI();
 
   69        }
catch(\RuntimeException $e){
 
   70            $errors[] = $e->getMessage();
 
   71            $authServiceUri = self::AUTHORIZATION_SERVICE_URI_FALLBACK;
 
   75            $openIdConfig = $this->getOpenIdConfiguration($authServiceUri);
 
   76            $jwksUri = $openIdConfig->jwks_uri;
 
   78            $this->issuer = $openIdConfig->issuer;
 
   79        } 
catch (\RuntimeException $e) {
 
   80            $errors[] = $e->getMessage();
 
   81            $jwksUri = $authServiceUri . self::AUTHORIZATION_SERVICE_KEYS_PATH;
 
   83            $this->issuer = $authServiceUri;
 
   87            $this->keys = 
new NonThreadSafeValue($this->getKeys($jwksUri));
 
   88        }
catch(\RuntimeException $e){
 
   89            $errors[] = $e->getMessage();
 
   92        $this->errors = $errors === [] ? null : 
new NonThreadSafeValue($errors);
 
 
   95    private function getAuthServiceURI() : string{
 
   96        $result = Internet::getURL(self::MINECRAFT_SERVICES_DISCOVERY_URL);
 
   97        if($result === 
null || $result->getCode() !== 200){
 
   98            throw new \RuntimeException(
"Failed to fetch Minecraft services discovery document");
 
  102            $json = json_decode($result->getBody(), false, flags: JSON_THROW_ON_ERROR);
 
  103        }
catch(\JsonException $e){
 
  104            throw new \RuntimeException($e->getMessage(), 0, $e);
 
  106        if(!is_object($json)){
 
  107            throw new \RuntimeException(
"Unexpected root type of schema file " . gettype($json) . 
", expected object");
 
  110        $mapper = new \JsonMapper();
 
  111        $mapper->bExceptionOnUndefinedProperty = 
false; 
 
  112        $mapper->bExceptionOnMissingData = 
true;
 
  113        $mapper->bStrictObjectTypeChecking = 
true;
 
  114        $mapper->bEnforceMapType = 
false;
 
  115        $mapper->bRemoveUndefinedAttributes = 
true;
 
  118            $discovery = $mapper->map($json, 
new MinecraftServicesDiscovery());
 
  119        }
catch(\JsonMapper_Exception $e){
 
  120            throw new \RuntimeException(
"Invalid schema file: " . $e->getMessage(), 0, $e);
 
  123        return $discovery->result->serviceEnvironments->auth->prod->serviceUri;
 
  126    private function getOpenIdConfiguration(
string $authServiceUri) : AuthServiceOpenIdConfiguration{
 
  127        $result = Internet::getURL($authServiceUri . self::AUTHORIZATION_SERVICE_OPENID_CONFIGURATION_PATH);
 
  128        if($result === 
null || $result->getCode() !== 200){
 
  129            throw new \RuntimeException(
"Failed to fetch OpenID configuration from authorization service");
 
  133            $json = json_decode($result->getBody(), false, flags: JSON_THROW_ON_ERROR);
 
  134        }
catch(\JsonException $e){
 
  135            throw new \RuntimeException($e->getMessage(), 0, $e);
 
  137        if(!is_object($json)){
 
  138            throw new \RuntimeException(
"Unexpected root type of schema file " . gettype($json) . 
", expected object");
 
  141        $mapper = new \JsonMapper();
 
  142        $mapper->bExceptionOnUndefinedProperty = 
false; 
 
  143        $mapper->bExceptionOnMissingData = 
true;
 
  144        $mapper->bStrictObjectTypeChecking = 
true;
 
  145        $mapper->bEnforceMapType = 
false;
 
  146        $mapper->bRemoveUndefinedAttributes = 
true;
 
  149            $configuration = $mapper->map($json, 
new AuthServiceOpenIdConfiguration());
 
  150        }
catch(\JsonMapper_Exception $e){
 
  151            throw new \RuntimeException(
"Invalid schema file: " . $e->getMessage(), 0, $e);
 
  154        return $configuration;
 
  160    private function getKeys(
string $jwksUri) : array{
 
  161        $result = Internet::getURL($jwksUri);
 
  162        if($result === 
null || $result->getCode() !== 200){
 
  163            return throw new \RuntimeException(
"Failed to fetch keys from authorization service");
 
  167            $json = json_decode($result->getBody(), true, flags: JSON_THROW_ON_ERROR);
 
  168        }
catch(\JsonException $e){
 
  169            throw new \RuntimeException($e->getMessage(), 0, $e);
 
  172        if(!is_array($json) || !isset($json[
"keys"]) || !is_array($keysArray = $json[
"keys"])){
 
  173            throw new \RuntimeException(
"Unexpected root type of schema file " . gettype($json) . 
", expected object");
 
  176        $mapper = new \JsonMapper();
 
  177        $mapper->bExceptionOnUndefinedProperty = 
true;
 
  178        $mapper->bExceptionOnMissingData = 
true;
 
  179        $mapper->bStrictObjectTypeChecking = 
true;
 
  180        $mapper->bEnforceMapType = 
false;
 
  181        $mapper->bRemoveUndefinedAttributes = 
true;
 
  184        foreach($keysArray as $keyJson){
 
  185            if(!is_array($keyJson)){
 
  186                throw new \RuntimeException(
"Unexpected key type in schema file: " . gettype($keyJson) . 
", expected object");
 
  191                $key = $mapper->map($keyJson, 
new AuthServiceKey());
 
  192                $keys[$key->kid] = $key;
 
  193            }
catch(\JsonMapper_Exception $e){
 
  194                throw new \RuntimeException(
"Invalid schema file: " . $e->getMessage(), 0, $e);
 
  206        $callback = $this->fetchLocal(self::KEYS_ON_COMPLETION);
 
  207        $callback($this->keys?->deserialize(), $this->issuer, $this->errors?->deserialize());