vendor/kreait/firebase-php/src/Firebase/Factory.php line 516

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Kreait\Firebase;
  4. use Firebase\Auth\Token\Cache\InMemoryCache;
  5. use Firebase\Auth\Token\Domain\Generator;
  6. use Firebase\Auth\Token\Domain\Verifier;
  7. use Firebase\Auth\Token\Generator as CustomTokenGenerator;
  8. use Firebase\Auth\Token\HttpKeyStore;
  9. use Firebase\Auth\Token\TenantAwareGenerator;
  10. use Firebase\Auth\Token\TenantAwareVerifier;
  11. use Firebase\Auth\Token\Verifier as LegacyIdTokenVerifier;
  12. use Google\Auth\ApplicationDefaultCredentials;
  13. use Google\Auth\Cache\MemoryCacheItemPool;
  14. use Google\Auth\Credentials\AppIdentityCredentials;
  15. use Google\Auth\Credentials\GCECredentials;
  16. use Google\Auth\Credentials\ServiceAccountCredentials;
  17. use Google\Auth\Credentials\UserRefreshCredentials;
  18. use Google\Auth\CredentialsLoader;
  19. use Google\Auth\FetchAuthTokenCache;
  20. use Google\Auth\FetchAuthTokenInterface;
  21. use Google\Auth\HttpHandler\HttpHandlerFactory;
  22. use Google\Auth\Middleware\AuthTokenMiddleware;
  23. use Google\Auth\ProjectIdProviderInterface;
  24. use Google\Auth\SignBlobInterface;
  25. use Google\Cloud\Firestore\FirestoreClient;
  26. use Google\Cloud\Storage\StorageClient;
  27. use GuzzleHttp\Client;
  28. use GuzzleHttp\HandlerStack;
  29. use GuzzleHttp\MessageFormatter;
  30. use GuzzleHttp\Psr7\Utils as GuzzleUtils;
  31. use GuzzleHttp\RequestOptions;
  32. use Kreait\Clock;
  33. use Kreait\Clock\SystemClock;
  34. use Kreait\Firebase;
  35. use Kreait\Firebase\Auth\CustomTokenViaGoogleIam;
  36. use Kreait\Firebase\Auth\DisabledLegacyCustomTokenGenerator;
  37. use Kreait\Firebase\Auth\DisabledLegacyIdTokenVerifier;
  38. use Kreait\Firebase\Auth\IdTokenVerifier;
  39. use Kreait\Firebase\Auth\TenantId;
  40. use Kreait\Firebase\Exception\InvalidArgumentException;
  41. use Kreait\Firebase\Exception\MessagingApiExceptionConverter;
  42. use Kreait\Firebase\Exception\RuntimeException;
  43. use Kreait\Firebase\Http\HttpClientOptions;
  44. use Kreait\Firebase\Http\Middleware;
  45. use Kreait\Firebase\Project\ProjectId;
  46. use Kreait\Firebase\Value\Email;
  47. use Kreait\Firebase\Value\Url;
  48. use Psr\Cache\CacheItemPoolInterface;
  49. use Psr\Http\Message\UriInterface;
  50. use Psr\Log\LoggerInterface;
  51. use Psr\Log\LogLevel;
  52. use Psr\SimpleCache\CacheInterface;
  53. use Throwable;
  54. class Factory
  55. {
  56.     public const API_CLIENT_SCOPES = [
  57.         'https://www.googleapis.com/auth/iam',
  58.         'https://www.googleapis.com/auth/cloud-platform',
  59.         'https://www.googleapis.com/auth/firebase',
  60.         'https://www.googleapis.com/auth/firebase.database',
  61.         'https://www.googleapis.com/auth/firebase.messaging',
  62.         'https://www.googleapis.com/auth/firebase.remoteconfig',
  63.         'https://www.googleapis.com/auth/userinfo.email',
  64.         'https://www.googleapis.com/auth/securetoken',
  65.     ];
  66.     protected ?UriInterface $databaseUri null;
  67.     protected ?string $defaultStorageBucket null;
  68.     protected ?ServiceAccount $serviceAccount null;
  69.     protected ?FetchAuthTokenInterface $googleAuthTokenCredentials null;
  70.     protected ?ProjectId $projectId null;
  71.     protected ?Email $clientEmail null;
  72.     protected CacheInterface $verifierCache;
  73.     protected CacheItemPoolInterface $authTokenCache;
  74.     protected bool $discoveryIsDisabled false;
  75.     protected bool $guzzleDebugModeIsEnabled false;
  76.     /**
  77.      * @deprecated 5.7.0 Use {@see withClientOptions} instead.
  78.      */
  79.     protected ?string $httpProxy null;
  80.     protected static string $databaseUriPattern 'https://%s.firebaseio.com';
  81.     protected static string $storageBucketNamePattern '%s.appspot.com';
  82.     protected Clock $clock;
  83.     /** @var callable|null */
  84.     protected $httpLogMiddleware;
  85.     /** @var callable|null */
  86.     protected $httpDebugLogMiddleware;
  87.     /** @var callable|null */
  88.     protected $databaseAuthVariableOverrideMiddleware;
  89.     protected ?TenantId $tenantId null;
  90.     protected HttpClientOptions $httpClientOptions;
  91.     public function __construct()
  92.     {
  93.         $this->clock = new SystemClock();
  94.         $this->verifierCache = new InMemoryCache();
  95.         $this->authTokenCache = new MemoryCacheItemPool();
  96.         $this->httpClientOptions HttpClientOptions::default();
  97.     }
  98.     /**
  99.      * @param string|array<string, string>|ServiceAccount $value
  100.      */
  101.     public function withServiceAccount($value): self
  102.     {
  103.         $serviceAccount ServiceAccount::fromValue($value);
  104.         $factory = clone $this;
  105.         $factory->serviceAccount $serviceAccount;
  106.         return $factory
  107.             ->withProjectId($serviceAccount->getProjectId())
  108.             ->withClientEmail($serviceAccount->getClientEmail())
  109.         ;
  110.     }
  111.     public function withProjectId(string $projectId): self
  112.     {
  113.         $factory = clone $this;
  114.         $factory->projectId ProjectId::fromString($projectId);
  115.         return $factory;
  116.     }
  117.     public function withClientEmail(string $clientEmail): self
  118.     {
  119.         $factory = clone $this;
  120.         $factory->clientEmail = new Email($clientEmail);
  121.         return $factory;
  122.     }
  123.     public function withTenantId(string $tenantId): self
  124.     {
  125.         $factory = clone $this;
  126.         $factory->tenantId TenantId::fromString($tenantId);
  127.         return $factory;
  128.     }
  129.     public function withDisabledAutoDiscovery(): self
  130.     {
  131.         $factory = clone $this;
  132.         $factory->discoveryIsDisabled true;
  133.         return $factory;
  134.     }
  135.     /**
  136.      * @param UriInterface|string $uri
  137.      */
  138.     public function withDatabaseUri($uri): self
  139.     {
  140.         $factory = clone $this;
  141.         $factory->databaseUri GuzzleUtils::uriFor($uri);
  142.         return $factory;
  143.     }
  144.     /**
  145.      * The object to use as the `auth` variable in your Realtime Database Rules
  146.      * when the Admin SDK reads from or writes to the Realtime Database.
  147.      *
  148.      * This allows you to downscope the Admin SDK from its default full read and
  149.      * write privileges. You can pass `null` to act as an unauthenticated client.
  150.      *
  151.      * @see https://firebase.google.com/docs/database/admin/start#authenticate-with-limited-privileges
  152.      *
  153.      * @param array<string, mixed>|null $override
  154.      */
  155.     public function withDatabaseAuthVariableOverride(?array $override): self
  156.     {
  157.         $factory = clone $this;
  158.         $factory->databaseAuthVariableOverrideMiddleware Middleware::addDatabaseAuthVariableOverride($override);
  159.         return $factory;
  160.     }
  161.     public function withDefaultStorageBucket(string $name): self
  162.     {
  163.         $factory = clone $this;
  164.         $factory->defaultStorageBucket $name;
  165.         return $factory;
  166.     }
  167.     public function withVerifierCache(CacheInterface $cache): self
  168.     {
  169.         $factory = clone $this;
  170.         $factory->verifierCache $cache;
  171.         return $factory;
  172.     }
  173.     public function withAuthTokenCache(CacheItemPoolInterface $cache): self
  174.     {
  175.         $factory = clone $this;
  176.         $factory->authTokenCache $cache;
  177.         return $factory;
  178.     }
  179.     public function withEnabledDebug(?LoggerInterface $logger null): self
  180.     {
  181.         $factory = clone $this;
  182.         if ($logger !== null) {
  183.             $factory $factory->withHttpDebugLogger($logger);
  184.         } else {
  185.             Firebase\Util\Deprecation::trigger(__METHOD__.' without a '.LoggerInterface::class);
  186.             // @codeCoverageIgnoreStart
  187.             $factory->guzzleDebugModeIsEnabled true;
  188.             // @codeCoverageIgnoreEnd
  189.         }
  190.         return $factory;
  191.     }
  192.     public function withHttpClientOptions(HttpClientOptions $options): self
  193.     {
  194.         $factory = clone $this;
  195.         $factory->httpClientOptions $options;
  196.         return $factory;
  197.     }
  198.     public function withHttpLogger(LoggerInterface $logger, ?MessageFormatter $formatter null, ?string $logLevel null, ?string $errorLogLevel null): self
  199.     {
  200.         $formatter $formatter ?: new MessageFormatter();
  201.         $logLevel $logLevel ?: LogLevel::INFO;
  202.         $errorLogLevel $errorLogLevel ?: LogLevel::NOTICE;
  203.         $factory = clone $this;
  204.         $factory->httpLogMiddleware Middleware::log($logger$formatter$logLevel$errorLogLevel);
  205.         return $factory;
  206.     }
  207.     public function withHttpDebugLogger(LoggerInterface $logger, ?MessageFormatter $formatter null, ?string $logLevel null, ?string $errorLogLevel null): self
  208.     {
  209.         $formatter $formatter ?: new MessageFormatter(MessageFormatter::DEBUG);
  210.         $logLevel $logLevel ?: LogLevel::INFO;
  211.         $errorLogLevel $errorLogLevel ?: LogLevel::NOTICE;
  212.         $factory = clone $this;
  213.         $factory->httpDebugLogMiddleware Middleware::log($logger$formatter$logLevel$errorLogLevel);
  214.         return $factory;
  215.     }
  216.     public function withHttpProxy(string $proxy): self
  217.     {
  218.         $factory $this->withHttpClientOptions(
  219.             $this->httpClientOptions->withProxy($proxy)
  220.         );
  221.         $factory->httpProxy $factory->httpClientOptions->proxy();
  222.         return $factory;
  223.     }
  224.     public function withClock(Clock $clock): self
  225.     {
  226.         $factory = clone $this;
  227.         $factory->clock $clock;
  228.         return $factory;
  229.     }
  230.     protected function getServiceAccount(): ?ServiceAccount
  231.     {
  232.         if ($this->serviceAccount !== null) {
  233.             return $this->serviceAccount;
  234.         }
  235.         if ($credentials Util::getenv('FIREBASE_CREDENTIALS')) {
  236.             return $this->serviceAccount ServiceAccount::fromValue($credentials);
  237.         }
  238.         if ($this->discoveryIsDisabled) {
  239.             return null;
  240.         }
  241.         if ($credentials Util::getenv('GOOGLE_APPLICATION_CREDENTIALS')) {
  242.             try {
  243.                 return $this->serviceAccount ServiceAccount::fromValue($credentials);
  244.             } catch (InvalidArgumentException $e) {
  245.                 // Do nothing, continue trying
  246.             }
  247.         }
  248.         // @codeCoverageIgnoreStart
  249.         // We can't reliably test this without re-implementing it ourselves
  250.         if ($credentials CredentialsLoader::fromWellKnownFile()) {
  251.             try {
  252.                 return $this->serviceAccount ServiceAccount::fromValue($credentials);
  253.             } catch (InvalidArgumentException $e) {
  254.                 // Do nothing, continue trying
  255.             }
  256.         }
  257.         // @codeCoverageIgnoreEnd
  258.         // ... or don't
  259.         return null;
  260.     }
  261.     protected function getProjectId(): ?ProjectId
  262.     {
  263.         if ($this->projectId !== null) {
  264.             return $this->projectId;
  265.         }
  266.         $serviceAccount $this->getServiceAccount();
  267.         if ($serviceAccount !== null) {
  268.             return $this->projectId ProjectId::fromString($serviceAccount->getProjectId());
  269.         }
  270.         if ($this->discoveryIsDisabled) {
  271.             return null;
  272.         }
  273.         if (
  274.             ($credentials $this->getGoogleAuthTokenCredentials())
  275.             && ($credentials instanceof ProjectIdProviderInterface)
  276.             && ($projectId $credentials->getProjectId())
  277.         ) {
  278.             return $this->projectId ProjectId::fromString($projectId);
  279.         }
  280.         if ($projectId Util::getenv('GOOGLE_CLOUD_PROJECT')) {
  281.             return $this->projectId ProjectId::fromString($projectId);
  282.         }
  283.         if ($projectId Util::getenv('GCLOUD_PROJECT')) {
  284.             return $this->projectId ProjectId::fromString($projectId);
  285.         }
  286.         return null;
  287.     }
  288.     protected function getClientEmail(): ?Email
  289.     {
  290.         if ($this->clientEmail !== null) {
  291.             return $this->clientEmail;
  292.         }
  293.         $serviceAccount $this->getServiceAccount();
  294.         if ($serviceAccount !== null) {
  295.             return $this->clientEmail = new Email($serviceAccount->getClientEmail());
  296.         }
  297.         if ($this->discoveryIsDisabled) {
  298.             return null;
  299.         }
  300.         try {
  301.             if (
  302.                 ($credentials $this->getGoogleAuthTokenCredentials())
  303.                 && ($credentials instanceof SignBlobInterface)
  304.                 && ($clientEmail $credentials->getClientName())
  305.             ) {
  306.                 return $this->clientEmail = new Email($clientEmail);
  307.             }
  308.         } catch (Throwable $e) {
  309.             return null;
  310.         }
  311.         return null;
  312.     }
  313.     protected function getDatabaseUri(): UriInterface
  314.     {
  315.         if ($this->databaseUri !== null) {
  316.             return $this->databaseUri;
  317.         }
  318.         $projectId $this->getProjectId();
  319.         if ($projectId !== null) {
  320.             return $this->databaseUri GuzzleUtils::uriFor(\sprintf(self::$databaseUriPattern$projectId->sanitizedValue()));
  321.         }
  322.         throw new RuntimeException('Unable to build a database URI without a project ID');
  323.     }
  324.     protected function getStorageBucketName(): ?string
  325.     {
  326.         if ($this->defaultStorageBucket) {
  327.             return $this->defaultStorageBucket;
  328.         }
  329.         $projectId $this->getProjectId();
  330.         if ($projectId !== null) {
  331.             return $this->defaultStorageBucket \sprintf(self::$storageBucketNamePattern$projectId->sanitizedValue());
  332.         }
  333.         return null;
  334.     }
  335.     public function createAuth(): Contract\Auth
  336.     {
  337.         $projectId $this->getProjectId();
  338.         $tenantId $this->tenantId;
  339.         $httpClient $this->createApiClient([
  340.             'base_uri' => 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/',
  341.         ]);
  342.         $authApiClient = new Auth\ApiClient($httpClient$tenantId);
  343.         $customTokenGenerator $this->createCustomTokenGenerator();
  344.         $idTokenVerifier $this->createIdTokenVerifier();
  345.         $signInHandler = new Firebase\Auth\SignIn\GuzzleHandler($httpClient);
  346.         return new Auth($authApiClient$httpClient$customTokenGenerator$idTokenVerifier$signInHandler$tenantId$projectId);
  347.     }
  348.     public function createCustomTokenGenerator(): Generator
  349.     {
  350.         $serviceAccount $this->getServiceAccount();
  351.         $clientEmail $this->getClientEmail();
  352.         $privateKey $serviceAccount !== null $serviceAccount->getPrivateKey() : '';
  353.         if ($clientEmail && $privateKey !== '') {
  354.             if ($this->tenantId !== null) {
  355.                 return new TenantAwareGenerator($this->tenantId->toString(), (string) $clientEmail$privateKey);
  356.             }
  357.             return new CustomTokenGenerator((string) $clientEmail$privateKey);
  358.         }
  359.         if ($clientEmail !== null) {
  360.             return new CustomTokenViaGoogleIam((string) $clientEmail$this->createApiClient(), $this->tenantId);
  361.         }
  362.         return new DisabledLegacyCustomTokenGenerator(
  363.             'Custom Token Generation is disabled because the current credentials do not permit it'
  364.         );
  365.     }
  366.     public function createIdTokenVerifier(): Verifier
  367.     {
  368.         $projectId $this->getProjectId();
  369.         if (!($projectId instanceof ProjectId)) {
  370.             return new DisabledLegacyIdTokenVerifier(
  371.                 'ID Token Verification is disabled because no project ID was provided'
  372.             );
  373.         }
  374.         $keyStore = new HttpKeyStore(new Client(), $this->verifierCache);
  375.         $baseVerifier = new LegacyIdTokenVerifier($projectId->sanitizedValue(), $keyStore);
  376.         if ($this->tenantId !== null) {
  377.             $baseVerifier = new TenantAwareVerifier($this->tenantId->toString(), $baseVerifier);
  378.         }
  379.         return new IdTokenVerifier($baseVerifier$this->clock);
  380.     }
  381.     public function createDatabase(): Contract\Database
  382.     {
  383.         $http $this->createApiClient();
  384.         /** @var HandlerStack $handler */
  385.         $handler $http->getConfig('handler');
  386.         $handler->push(Firebase\Http\Middleware::ensureJsonSuffix(), 'realtime_database_json_suffix');
  387.         if ($this->databaseAuthVariableOverrideMiddleware) {
  388.             $handler->push($this->databaseAuthVariableOverrideMiddleware'database_auth_variable_override');
  389.         }
  390.         return new Database($this->getDatabaseUri(), new Database\ApiClient($http));
  391.     }
  392.     public function createRemoteConfig(): Contract\RemoteConfig
  393.     {
  394.         $projectId $this->getProjectId();
  395.         if (!($projectId instanceof ProjectId)) {
  396.             throw new RuntimeException('Unable to create the messaging service without a project ID');
  397.         }
  398.         $http $this->createApiClient([
  399.             'base_uri' => "https://firebaseremoteconfig.googleapis.com/v1/projects/{$projectId->value()}/remoteConfig",
  400.         ]);
  401.         return new RemoteConfig(new RemoteConfig\ApiClient($http));
  402.     }
  403.     public function createMessaging(): Contract\Messaging
  404.     {
  405.         $projectId $this->getProjectId();
  406.         if (!($projectId instanceof ProjectId)) {
  407.             throw new RuntimeException('Unable to create the messaging service without a project ID');
  408.         }
  409.         $errorHandler = new MessagingApiExceptionConverter($this->clock);
  410.         $messagingApiClient = new Messaging\ApiClient(
  411.             $this->createApiClient([
  412.                 'base_uri' => 'https://fcm.googleapis.com/v1/projects/'.$projectId->value(),
  413.             ]),
  414.             $errorHandler
  415.         );
  416.         $appInstanceApiClient = new Messaging\AppInstanceApiClient(
  417.             $this->createApiClient([
  418.                 'base_uri' => 'https://iid.googleapis.com',
  419.                 'headers' => [
  420.                     'access_token_auth' => 'true',
  421.                 ],
  422.             ]),
  423.             $errorHandler
  424.         );
  425.         return new Messaging($projectId$messagingApiClient$appInstanceApiClient);
  426.     }
  427.     /**
  428.      * @param string|Url|UriInterface|mixed $defaultDynamicLinksDomain
  429.      */
  430.     public function createDynamicLinksService($defaultDynamicLinksDomain null): Contract\DynamicLinks
  431.     {
  432.         $apiClient $this->createApiClient();
  433.         if ($defaultDynamicLinksDomain) {
  434.             return DynamicLinks::withApiClientAndDefaultDomain($apiClient$defaultDynamicLinksDomain);
  435.         }
  436.         return DynamicLinks::withApiClient($apiClient);
  437.     }
  438.     public function createFirestore(): Contract\Firestore
  439.     {
  440.         $config = [];
  441.         $projectId $this->getProjectId();
  442.         $serviceAccount $this->getServiceAccount();
  443.         if ($serviceAccount !== null) {
  444.             $config['keyFile'] = $serviceAccount->asArray();
  445.         } elseif ($this->discoveryIsDisabled) {
  446.             throw new RuntimeException('Unable to create a Firestore Client without credentials');
  447.         }
  448.         if ($projectId !== null) {
  449.             $config['projectId'] = $projectId->value();
  450.         }
  451.         if (!($projectId instanceof ProjectId)) {
  452.             // This is the case with user refresh credentials
  453.             $config['suppressKeyFileNotice'] = true;
  454.         }
  455.         try {
  456.             $firestoreClient = new FirestoreClient($config);
  457.         } catch (Throwable $e) {
  458.             throw new RuntimeException('Unable to create a FirestoreClient: '.$e->getMessage(), $e->getCode(), $e);
  459.         }
  460.         return Firestore::withFirestoreClient($firestoreClient);
  461.     }
  462.     public function createStorage(): Contract\Storage
  463.     {
  464.         $config = [];
  465.         $projectId $this->getProjectId();
  466.         $serviceAccount $this->getServiceAccount();
  467.         if ($serviceAccount !== null) {
  468.             $config['keyFile'] = $serviceAccount->asArray();
  469.         } elseif ($this->discoveryIsDisabled) {
  470.             throw new RuntimeException('Unable to create a Storage Client without credentials');
  471.         }
  472.         if ($projectId instanceof ProjectId) {
  473.             $config['projectId'] = $projectId->value();
  474.         } else {
  475.             // This is the case with user refresh credentials
  476.             $config['suppressKeyFileNotice'] = true;
  477.         }
  478.         try {
  479.             $storageClient = new StorageClient($config);
  480.         } catch (Throwable $e) {
  481.             throw new RuntimeException('Unable to create a Storage Client: '.$e->getMessage(), $e->getCode(), $e);
  482.         }
  483.         return new Storage($storageClient$this->getStorageBucketName());
  484.     }
  485.     /**
  486.      * @codeCoverageIgnore
  487.      *
  488.      * @return array{
  489.      *     credentialsType: class-string|null,
  490.      *     databaseUrl: string,
  491.      *     defaultStorageBucket: string|null,
  492.      *     serviceAccount: array{
  493.      *         client_email: string|null,
  494.      *         private_key: string|null,
  495.      *         project_id: string|null,
  496.      *         type: string
  497.      *     }|array<string, string|null>|null,
  498.      *     projectId: string|null,
  499.      *     tenantId: string|null,
  500.      *     verifierCacheType: class-string|null,
  501.      * }
  502.      */
  503.     public function getDebugInfo(): array
  504.     {
  505.         $credentials $this->getGoogleAuthTokenCredentials();
  506.         $projectId $this->getProjectId();
  507.         $serviceAccount $this->getServiceAccount();
  508.         try {
  509.             $databaseUrl = (string) $this->getDatabaseUri();
  510.         } catch (Throwable $e) {
  511.             $databaseUrl $e->getMessage();
  512.         }
  513.         $serviceAccountInfo null;
  514.         if ($serviceAccount !== null) {
  515.             $serviceAccountInfo $serviceAccount->asArray();
  516.             $serviceAccountInfo['private_key'] = $serviceAccountInfo['private_key'] ? '{exists, redacted}' '{not set}';
  517.         }
  518.         return [
  519.             'credentialsType' => $credentials !== null \get_class($credentials) : null,
  520.             'databaseUrl' => $databaseUrl,
  521.             'defaultStorageBucket' => $this->defaultStorageBucket,
  522.             'projectId' => $projectId !== null $projectId->value() : null,
  523.             'serviceAccount' => $serviceAccountInfo,
  524.             'tenantId' => $this->tenantId !== null $this->tenantId->toString() : null,
  525.             'tokenCacheType' => \get_class($this->authTokenCache),
  526.             'verifierCacheType' => \get_class($this->verifierCache),
  527.         ];
  528.     }
  529.     /**
  530.      * @internal
  531.      *
  532.      * @param array<string, mixed>|null $config
  533.      */
  534.     public function createApiClient(?array $config null): Client
  535.     {
  536.         $config ??= [];
  537.         // @codeCoverageIgnoreStart
  538.         if ($this->guzzleDebugModeIsEnabled) {
  539.             $config[RequestOptions::DEBUG] = true;
  540.         }
  541.         // @codeCoverageIgnoreEnd
  542.         if ($proxy $this->httpClientOptions->proxy()) {
  543.             $config[RequestOptions::PROXY] = $proxy;
  544.         }
  545.         if ($connectTimeout $this->httpClientOptions->connectTimeout()) {
  546.             $config[RequestOptions::CONNECT_TIMEOUT] = $connectTimeout;
  547.         }
  548.         if ($readTimeout $this->httpClientOptions->readTimeout()) {
  549.             $config[RequestOptions::READ_TIMEOUT] = $readTimeout;
  550.         }
  551.         if ($totalTimeout $this->httpClientOptions->timeout()) {
  552.             $config[RequestOptions::TIMEOUT] = $totalTimeout;
  553.         }
  554.         $handler $config['handler'] ?? null;
  555.         if (!($handler instanceof HandlerStack)) {
  556.             $handler HandlerStack::create($handler);
  557.         }
  558.         if ($this->httpLogMiddleware) {
  559.             $handler->push($this->httpLogMiddleware'http_logs');
  560.         }
  561.         if ($this->httpDebugLogMiddleware) {
  562.             $handler->push($this->httpDebugLogMiddleware'http_debug_logs');
  563.         }
  564.         $credentials $this->getGoogleAuthTokenCredentials();
  565.         if ($credentials !== null) {
  566.             $projectId $credentials instanceof ProjectIdProviderInterface $credentials->getProjectId() : 'project';
  567.             $cachePrefix 'kreait_firebase_'.$projectId;
  568.             $credentials = new FetchAuthTokenCache($credentials, ['prefix' => $cachePrefix], $this->authTokenCache);
  569.             $authTokenHandlerConfig $config;
  570.             $authTokenHandlerConfig['handler'] = clone $handler;
  571.             $authTokenHandler HttpHandlerFactory::build(new Client($authTokenHandlerConfig));
  572.             $handler->push(new AuthTokenMiddleware($credentials$authTokenHandler));
  573.         }
  574.         $handler->push(Middleware::responseWithSubResponses());
  575.         $config['handler'] = $handler;
  576.         $config['auth'] = 'google_auth';
  577.         return new Client($config);
  578.     }
  579.     /**
  580.      * @internal
  581.      *
  582.      * @param ServiceAccountCredentials|UserRefreshCredentials|AppIdentityCredentials|GCECredentials|CredentialsLoader $credentials
  583.      */
  584.     public function withGoogleAuthTokenCredentials($credentials): self
  585.     {
  586.         $factory = clone $this;
  587.         $factory->googleAuthTokenCredentials $credentials;
  588.         return $factory;
  589.     }
  590.     protected function getGoogleAuthTokenCredentials(): ?FetchAuthTokenInterface
  591.     {
  592.         if ($this->googleAuthTokenCredentials !== null) {
  593.             return $this->googleAuthTokenCredentials;
  594.         }
  595.         $serviceAccount $this->getServiceAccount();
  596.         if ($serviceAccount !== null) {
  597.             return $this->googleAuthTokenCredentials = new ServiceAccountCredentials(self::API_CLIENT_SCOPES$serviceAccount->asArray());
  598.         }
  599.         if ($this->discoveryIsDisabled) {
  600.             return null;
  601.         }
  602.         try {
  603.             return $this->googleAuthTokenCredentials ApplicationDefaultCredentials::getCredentials(self::API_CLIENT_SCOPES);
  604.         } catch (Throwable $e) {
  605.             return null;
  606.         }
  607.     }
  608. }