allow vendord
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 3m3s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 16s
Build, Push and Deploy / deploy-staging (push) Successful in 32s
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 1s

This commit is contained in:
2026-03-30 14:54:57 +07:00
parent 66aed7c4e8
commit b5e3a778ce
21316 changed files with 2777892 additions and 13 deletions

View File

@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\DynamicLink;
use JsonSerializable;
use Kreait\Firebase\DynamicLink\AnalyticsInfo\GooglePlayAnalytics;
use Kreait\Firebase\DynamicLink\AnalyticsInfo\ITunesConnectAnalytics;
use function array_key_exists;
/**
* @phpstan-import-type GooglePlayAnalyticsShape from GooglePlayAnalytics
* @phpstan-import-type ITunesConnectAnalyticsShape from ITunesConnectAnalytics
*
* @phpstan-type AnalyticsInfoShape array{
* googlePlayAnalytics?: GooglePlayAnalyticsShape,
* itunesConnectAnalytics?: ITunesConnectAnalyticsShape
* }
*/
final class AnalyticsInfo implements JsonSerializable
{
private function __construct(
private readonly ?GooglePlayAnalytics $googlePlayAnalytics,
private readonly ?ITunesConnectAnalytics $iTunesConnectAnalytics,
) {
}
/**
* @param AnalyticsInfoShape $data
*/
public static function fromArray(array $data): self
{
$googlePlayAnalytics = array_key_exists('googlePlayAnalytics', $data)
? GooglePlayAnalytics::fromArray($data['googlePlayAnalytics'])
: null;
$itunesConnectAnalytics = array_key_exists('itunesConnectAnalytics', $data)
? ITunesConnectAnalytics::fromArray($data['itunesConnectAnalytics'])
: null;
return new self($googlePlayAnalytics, $itunesConnectAnalytics);
}
public static function new(): self
{
return new self(null, null);
}
/**
* @param GooglePlayAnalytics|GooglePlayAnalyticsShape $data
*/
public function withGooglePlayAnalyticsInfo($data): self
{
$gpInfo = $data instanceof GooglePlayAnalytics ? $data : GooglePlayAnalytics::fromArray($data);
return new self($gpInfo, $this->iTunesConnectAnalytics);
}
/**
* @param ITunesConnectAnalytics|ITunesConnectAnalyticsShape $data
*/
public function withItunesConnectAnalytics($data): self
{
$icInfo = $data instanceof ITunesConnectAnalytics ? $data : ITunesConnectAnalytics::fromArray($data);
return new self($this->googlePlayAnalytics, $icInfo);
}
/**
* @return AnalyticsInfoShape
*/
public function jsonSerialize(): array
{
return array_filter([
'googlePlayAnalytics' => $this->googlePlayAnalytics?->jsonSerialize(),
'itunesConnectAnalytics' => $this->iTunesConnectAnalytics?->jsonSerialize(),
], fn(?array $value): bool => $value !== null);
}
}

View File

@@ -0,0 +1,139 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\DynamicLink\AnalyticsInfo;
use JsonSerializable;
/**
* @phpstan-type GooglePlayAnalyticsShape array{
* utmSource?: non-empty-string,
* utmMedium?: non-empty-string,
* utmCampaign?: non-empty-string,
* utmTerm?: non-empty-string,
* utmContent?: non-empty-string,
* gclid?: non-empty-string
* }
*/
final class GooglePlayAnalytics implements JsonSerializable
{
/**
* @param GooglePlayAnalyticsShape $data
*/
private function __construct(private readonly array $data)
{
}
/**
* @param GooglePlayAnalyticsShape $data
*/
public static function fromArray(array $data): self
{
return new self($data);
}
public static function new(): self
{
return new self([]);
}
/**
* Identifies the advertiser, site, publication, etc. that is sending traffic to your property,
* for example: google, newsletter4, billboard.
*
* @see https://support.google.com/analytics/answer/1033863#parameters
*
* @param non-empty-string $utmSource
*/
public function withUtmSource(string $utmSource): self
{
$data = $this->data;
$data['utmSource'] = $utmSource;
return new self($data);
}
/**
* The advertising or marketing medium, for example: cpc, banner, email newsletter.
*
* @see https://support.google.com/analytics/answer/1033863#parameters
*
* @param non-empty-string $utmMedium
*/
public function withUtmMedium(string $utmMedium): self
{
$data = $this->data;
$data['utmMedium'] = $utmMedium;
return new self($data);
}
/**
* The individual campaign name, slogan, promo code, etc. for a product.
*
* @see https://support.google.com/analytics/answer/1033863#parameters
*
* @param non-empty-string $utmCampaign
*/
public function withUtmCampaign(string $utmCampaign): self
{
$data = $this->data;
$data['utmCampaign'] = $utmCampaign;
return new self($data);
}
/**
* Identifies paid search keywords. If you're manually tagging paid keyword campaigns, you should also use
* utm_term to specify the keyword.
*
* @see https://support.google.com/analytics/answer/1033863#parameters
*
* @param non-empty-string $utmTerm
*/
public function withUtmTerm(string $utmTerm): self
{
$data = $this->data;
$data['utmTerm'] = $utmTerm;
return new self($data);
}
/**
* Used to differentiate similar content, or links within the same ad. For example, if you have two call-to-action
* links within the same email message, you can use utm_content and set different values for each, so you can tell
* which version is more effective.
*
* @see https://support.google.com/analytics/answer/1033863#parameters
*
* @param non-empty-string $utmContent
*/
public function withUtmContent(string $utmContent): self
{
$data = $this->data;
$data['utmContent'] = $utmContent;
return new self($data);
}
/**
* The Google Click ID.
*
* @see https://support.google.com/analytics/answer/2938246?hl=en
*
* @param non-empty-string $gclid
*/
public function withGclid(string $gclid): self
{
$data = $this->data;
$data['gclid'] = $gclid;
return new self($data);
}
public function jsonSerialize(): array
{
return $this->data;
}
}

View File

@@ -0,0 +1,106 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\DynamicLink\AnalyticsInfo;
use JsonSerializable;
/**
* @see https://www.macstories.net/tutorials/a-comprehensive-guide-to-the-itunes-affiliate-program/
* @see https://blog.geni.us/parameter-cheat-sheet-for-itunes-and-app-store-links/
*
* @phpstan-type ITunesConnectAnalyticsShape array{
* at?: non-empty-string,
* ct?: non-empty-string,
* mt?: non-empty-string,
* pt?: non-empty-string
* }
*/
final class ITunesConnectAnalytics implements JsonSerializable
{
/**
* @param ITunesConnectAnalyticsShape $data
*/
private function __construct(private readonly array $data)
{
}
/**
* @param ITunesConnectAnalyticsShape $data
*/
public static function fromArray(array $data): self
{
return new self($data);
}
public static function new(): self
{
return new self([]);
}
/**
* The iTunes connect/affiliate partner token.
*
* @see https://blog.geni.us/parameter-cheat-sheet-for-itunes-and-app-store-links/
*
* @param non-empty-string $affiliateToken
*/
public function withAffiliateToken(string $affiliateToken): self
{
$data = $this->data;
$data['at'] = $affiliateToken;
return new self($data);
}
/**
* The iTunes connect/affiliate partner token.
*
* @see https://blog.geni.us/parameter-cheat-sheet-for-itunes-and-app-store-links/
*
* @param non-empty-string $campaignToken
*/
public function withCampaignToken(string $campaignToken): self
{
$data = $this->data;
$data['ct'] = $campaignToken;
return new self($data);
}
/**
* The media type.
*
* @see https://blog.geni.us/parameter-cheat-sheet-for-itunes-and-app-store-links/
*
* @param non-empty-string $mediaType
*/
public function withMediaType(string $mediaType): self
{
$data = $this->data;
$data['mt'] = $mediaType;
return new self($data);
}
/**
* The provider token.
*
* @see https://www.macstories.net/tutorials/a-comprehensive-guide-to-the-itunes-affiliate-program/
*
* @param non-empty-string $providerToken
*/
public function withProviderToken(string $providerToken): self
{
$data = $this->data;
$data['pt'] = $providerToken;
return new self($data);
}
public function jsonSerialize(): array
{
return $this->data;
}
}

View File

@@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\DynamicLink;
use JsonSerializable;
/**
* @phpstan-type AndroidInfoShape array{
* androidPackageName?: non-empty-string,
* androidFallbackLink?: non-empty-string,
* androidMinPackageVersionCode?: non-empty-string
* }
*/
final class AndroidInfo implements JsonSerializable
{
/**
* @param AndroidInfoShape $data
*/
private function __construct(private readonly array $data)
{
}
/**
* @param AndroidInfoShape $data
*/
public static function fromArray(array $data): self
{
return new self($data);
}
public static function new(): self
{
return new self([]);
}
/**
* The package name of the Android app to use to open the link. The app must be connected to your project from the
* Overview page of the Firebase console. Required for the Dynamic Link to open an Android app.
*
* @param non-empty-string $packageName
*/
public function withPackageName(string $packageName): self
{
$data = $this->data;
$data['androidPackageName'] = $packageName;
return new self($data);
}
/**
* The link to open when the app isn't installed. Specify this to do something other than install your app
* from the Play Store when the app isn't installed, such as open the mobile web version of the content,
* or display a promotional page for your app.
*
* @param non-empty-string $fallbackLink
*/
public function withFallbackLink(string $fallbackLink): self
{
$data = $this->data;
$data['androidFallbackLink'] = $fallbackLink;
return new self($data);
}
/**
* The versionCode of the minimum version of your app that can open the link. If the installed app is an older
* version, the user is taken to the Play Store to upgrade the app.
*
* @see https://developer.android.com/studio/publish/versioning#appversioning
*
* @param non-empty-string $minPackageVersionCode
*/
public function withMinPackageVersionCode(string $minPackageVersionCode): self
{
$data = $this->data;
$data['androidMinPackageVersionCode'] = $minPackageVersionCode;
return new self($data);
}
public function jsonSerialize(): array
{
return $this->data;
}
}

View File

@@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\DynamicLink;
use Beste\Json;
use GuzzleHttp\ClientInterface;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamFactoryInterface;
use const JSON_FORCE_OBJECT;
/**
* @internal
*/
final class ApiClient
{
public function __construct(
private readonly ClientInterface $client,
private readonly RequestFactoryInterface $requestFactory,
private readonly StreamFactoryInterface $streamFactory,
) {
}
public function createDynamicLinkRequest(CreateDynamicLink $action): RequestInterface
{
return $this->requestFactory
->createRequest('POST', 'https://firebasedynamiclinks.googleapis.com/v1/shortLinks')
->withBody($this->streamFactory->createStream(Json::encode($action, JSON_FORCE_OBJECT)))
->withHeader('Content-Type', 'application/json; charset=UTF-8')
;
}
public function createStatisticsRequest(GetStatisticsForDynamicLink $action): RequestInterface
{
$url = sprintf(
'https://firebasedynamiclinks.googleapis.com/v1/%s/linkStats?durationDays=%d',
rawurlencode($action->dynamicLink()),
$action->durationInDays(),
);
return $this->requestFactory
->createRequest('GET', $url)
->withHeader('Content-Type', 'application/json; charset=UTF-8')
;
}
public function createShortenLinkRequest(ShortenLongDynamicLink $action): RequestInterface
{
return $this->requestFactory
->createRequest('POST', 'https://firebasedynamiclinks.googleapis.com/v1/shortLinks')
->withBody($this->streamFactory->createStream(Json::encode($action, JSON_FORCE_OBJECT)))
->withHeader('Content-Type', 'application/json; charset=UTF-8')
;
}
/**
* @param array<string, mixed> $options
*
* @throws ClientExceptionInterface
*/
public function send(RequestInterface $request, array $options = []): ResponseInterface
{
return $this->client->send($request, $options);
}
}

View File

@@ -0,0 +1,177 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\DynamicLink;
use Beste\Json;
use JsonSerializable;
use Kreait\Firebase\Value\Url;
use Stringable;
/**
* @phpstan-import-type AnalyticsInfoShape from AnalyticsInfo
* @phpstan-import-type AndroidInfoShape from AndroidInfo
* @phpstan-import-type IOSInfoShape from IOSInfo
* @phpstan-import-type NavigationInfoShape from NavigationInfo
* @phpstan-import-type SocialMetaTagInfoShape from SocialMetaTagInfo
*
* @phpstan-type CreateDynamicLinkShape array{
* dynamicLinkInfo: array{
* link?: non-empty-string,
* domainUriPrefix?: non-empty-string,
* analyticsInfo?: AnalyticsInfoShape,
* androidInfo?: AndroidInfoShape,
* iosInfo?: IOSInfoShape,
* navigationInfo?: NavigationInfoShape,
* socialMetaTagInfo?: SocialMetaTagInfoShape
* },
* suffix: array{
* option: self::WITH_*
* }
* }
*/
final class CreateDynamicLink implements JsonSerializable
{
public const WITH_UNGUESSABLE_SUFFIX = 'UNGUESSABLE';
public const WITH_SHORT_SUFFIX = 'SHORT';
/**
* @param CreateDynamicLinkShape $data
*/
private function __construct(private readonly array $data)
{
}
/**
* @param CreateDynamicLinkShape $data
*/
public static function fromArray(array $data): self
{
return new self($data);
}
public static function new(): self
{
return new self([
'dynamicLinkInfo' => [],
'suffix' => ['option' => self::WITH_UNGUESSABLE_SUFFIX],
]);
}
/**
* The link your app will open. Specify a URL that your app can handle, typically the app's content
* or payload, which initiates app-specific logic (such as crediting the user with a coupon or
* displaying a welcome screen). This link must be a well-formatted URL, be properly
* URL-encoded, use either HTTP or HTTPS, and cannot be another Dynamic Link.
*/
public static function forUrl(Stringable|string $url): self
{
return new self([
'dynamicLinkInfo' => [
'link' => Url::fromString($url)->value,
],
'suffix' => ['option' => self::WITH_UNGUESSABLE_SUFFIX],
]);
}
public function withDynamicLinkDomain(Stringable|string $dynamicLinkDomain): self
{
$data = $this->data;
$data['dynamicLinkInfo']['domainUriPrefix'] = Url::fromString($dynamicLinkDomain)->value;
return new self($data);
}
public function hasDynamicLinkDomain(): bool
{
return (bool) ($this->data['dynamicLinkInfo']['domainUriPrefix'] ?? null);
}
/**
* @param AnalyticsInfo|AnalyticsInfoShape $data
*/
public function withAnalyticsInfo(AnalyticsInfo|array $data): self
{
$info = $data instanceof AnalyticsInfo ? $data : AnalyticsInfo::fromArray($data);
$data = $this->data;
$data['dynamicLinkInfo']['analyticsInfo'] = Json::decode(Json::encode($info), true);
return new self($data);
}
/**
* @param AndroidInfo|AndroidInfoShape $data
*/
public function withAndroidInfo(AndroidInfo|array $data): self
{
$info = $data instanceof AndroidInfo ? $data : AndroidInfo::fromArray($data);
$data = $this->data;
$data['dynamicLinkInfo']['androidInfo'] = Json::decode(Json::encode($info), true);
return new self($data);
}
/**
* @param IOSInfo|IOSInfoShape $data
*/
public function withIOSInfo(IOSInfo|array $data): self
{
$info = $data instanceof IOSInfo ? $data : IOSInfo::fromArray($data);
$data = $this->data;
$data['dynamicLinkInfo']['iosInfo'] = Json::decode(Json::encode($info), true);
return new self($data);
}
/**
* @param NavigationInfo|NavigationInfoShape $data
*/
public function withNavigationInfo(NavigationInfo|array $data): self
{
$info = $data instanceof NavigationInfo ? $data : NavigationInfo::fromArray($data);
$data = $this->data;
$data['dynamicLinkInfo']['navigationInfo'] = Json::decode(Json::encode($info), true);
return new self($data);
}
/**
* @param SocialMetaTagInfo|SocialMetaTagInfoShape $data
*/
public function withSocialMetaTagInfo(SocialMetaTagInfo|array $data): self
{
$info = $data instanceof SocialMetaTagInfo ? $data : SocialMetaTagInfo::fromArray($data);
$data = $this->data;
$data['dynamicLinkInfo']['socialMetaTagInfo'] = Json::decode(Json::encode($info), true);
return new self($data);
}
public function withUnguessableSuffix(): self
{
$data = $this->data;
$data['suffix']['option'] = self::WITH_UNGUESSABLE_SUFFIX;
return new self($data);
}
public function withShortSuffix(): self
{
$data = $this->data;
$data['suffix']['option'] = self::WITH_SHORT_SUFFIX;
return new self($data);
}
public function jsonSerialize(): array
{
return $this->data;
}
}

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\DynamicLink\CreateDynamicLink;
use Beste\Json;
use Kreait\Firebase\DynamicLink\CreateDynamicLink;
use Kreait\Firebase\Exception\RuntimeException;
use Psr\Http\Message\ResponseInterface;
use UnexpectedValueException;
final class FailedToCreateDynamicLink extends RuntimeException
{
private ?CreateDynamicLink $action = null;
private ?ResponseInterface $response = null;
public static function withActionAndResponse(CreateDynamicLink $action, ResponseInterface $response): self
{
$fallbackMessage = 'Failed to create dynamic link';
try {
$message = Json::decode((string) $response->getBody(), true)['error']['message'] ?? $fallbackMessage;
} catch (UnexpectedValueException) {
$message = $fallbackMessage;
}
$error = new self($message);
$error->action = $action;
$error->response = $response;
return $error;
}
public function action(): ?CreateDynamicLink
{
return $this->action;
}
public function response(): ?ResponseInterface
{
return $this->response;
}
}

View File

@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\DynamicLink;
use Beste\Json;
use Psr\Http\Message\ResponseInterface;
final class DynamicLinkStatistics
{
/**
* @var array<string, list<array<string, string>>>
*/
private array $rawData = [];
private EventStatistics $events;
private function __construct()
{
$this->events = EventStatistics::fromArray([]);
}
/**
* @internal
*/
public static function fromApiResponse(ResponseInterface $response): self
{
$data = Json::decode((string) $response->getBody(), true);
$link = new self();
$link->rawData = $data;
$link->events = EventStatistics::fromArray($data['linkEventStats'] ?? []);
return $link;
}
public function eventStatistics(): EventStatistics
{
return $this->events;
}
/**
* @return array<string, list<array<string, string>>>
*/
public function rawData(): array
{
return $this->rawData;
}
}

View File

@@ -0,0 +1,133 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\DynamicLink;
use Countable;
use IteratorAggregate;
use Traversable;
use function array_filter;
/**
* @see https://firebase.google.com/docs/reference/dynamic-links/analytics#response_body
* @see https://github.com/googleapis/google-api-nodejs-client/blob/main/src/apis/firebasedynamiclinks/v1.ts
*
* @phpstan-type EventStatisticsShape array{
* linkEventStats: array{
* count?: non-empty-string,
* event?: non-empty-string,
* platform?: non-empty-string
* }
* }
*
* @implements IteratorAggregate<array>
*/
final class EventStatistics implements Countable, IteratorAggregate
{
public const PLATFORM_ANDROID = 'ANDROID';
public const PLATFORM_DESKTOP = 'DESKTOP';
public const PLATFORM_IOS = 'IOS';
// Any click on a Dynamic Link, irrespective to how it is handled and its destinations
public const TYPE_CLICK = 'CLICK';
// Attempts to redirect users, either to the App Store or Play Store to install or update the app,
// or to some other destination
public const TYPE_REDIRECT = 'REDIRECT';
// Actual installs (only supported by the Play Store)
public const TYPE_APP_INSTALL = 'APP_INSTALL';
// First-opens after an install
public const TYPE_APP_FIRST_OPEN = 'APP_FIRST_OPEN';
// Re-opens of an app
public const TYPE_APP_RE_OPEN = 'APP_RE_OPEN';
/**
* @param list<EventStatisticsShape> $events
*/
private function __construct(private readonly array $events)
{
}
/**
* @param list<EventStatisticsShape> $events
*/
public static function fromArray(array $events): self
{
return new self($events);
}
public function onAndroid(): self
{
return $this->filterByPlatform(self::PLATFORM_ANDROID);
}
public function onDesktop(): self
{
return $this->filterByPlatform(self::PLATFORM_DESKTOP);
}
public function onIOS(): self
{
return $this->filterByPlatform(self::PLATFORM_IOS);
}
public function clicks(): self
{
return $this->filterByType(self::TYPE_CLICK);
}
public function redirects(): self
{
return $this->filterByType(self::TYPE_REDIRECT);
}
public function appInstalls(): self
{
return $this->filterByType(self::TYPE_APP_INSTALL);
}
public function appFirstOpens(): self
{
return $this->filterByType(self::TYPE_APP_FIRST_OPEN);
}
public function appReOpens(): self
{
return $this->filterByType(self::TYPE_APP_RE_OPEN);
}
public function filterByType(string $type): self
{
return $this->filter(static fn(array $event): bool => ($event['event'] ?? null) === $type);
}
public function filterByPlatform(string $platform): self
{
return $this->filter(static fn(array $event): bool => ($event['platform'] ?? null) === $platform);
}
public function filter(callable $filter): self
{
return new self(array_values(array_filter($this->events, $filter)));
}
/**
* @return Traversable<EventStatisticsShape>
*/
public function getIterator(): Traversable
{
yield from $this->events;
}
public function count(): int
{
return array_sum(array_column($this->events, 'count'));
}
}

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\DynamicLink;
use Kreait\Firebase\Value\Url;
use Stringable;
final class GetStatisticsForDynamicLink
{
public const DEFAULT_DURATION_IN_DAYS = 7;
private int $durationInDays = self::DEFAULT_DURATION_IN_DAYS;
private function __construct(private readonly string $dynamicLink)
{
}
public static function forLink(Stringable|string $link): self
{
return new self(Url::fromString($link)->value);
}
public function withDurationInDays(int $durationInDays): self
{
$action = clone $this;
$action->durationInDays = $durationInDays;
return $action;
}
public function dynamicLink(): string
{
return $this->dynamicLink;
}
public function durationInDays(): int
{
return $this->durationInDays;
}
}

View File

@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\DynamicLink\GetStatisticsForDynamicLink;
use Fig\Http\Message\StatusCodeInterface as StatusCode;
use Kreait\Firebase\DynamicLink\GetStatisticsForDynamicLink;
use Kreait\Firebase\Exception\RuntimeException;
use Psr\Http\Message\ResponseInterface;
final class FailedToGetStatisticsForDynamicLink extends RuntimeException
{
private ?GetStatisticsForDynamicLink $action = null;
private ?ResponseInterface $response = null;
public static function withActionAndResponse(GetStatisticsForDynamicLink $action, ResponseInterface $response): self
{
['code' => $code, 'message' => $message] = self::getCodeAndMessageFromResponse($response);
$error = new self($message, $code);
$error->action = $action;
$error->response = $response;
return $error;
}
public function action(): ?GetStatisticsForDynamicLink
{
return $this->action;
}
public function response(): ?ResponseInterface
{
return $this->response;
}
/**
* @return array{
* code: int,
* message: string
* }
*/
private static function getCodeAndMessageFromResponse(ResponseInterface $response): array
{
$message = match ($code = $response->getStatusCode()) {
StatusCode::STATUS_FORBIDDEN => <<<'MSG'
Firebase reported missing permissions to access the statistics
for the requested Dynamic Link. Please make sure that the
Google Dynamic Links API is enabled for your project at
https://console.cloud.google.com/apis/library/firebasedynamiclinks.googleapis.com
If the API is enabled, you or the Service Account you're using
might be missing the required permissions. You can check by
visiting
https://console.cloud.google.com/iam-admin/serviceaccounts/
and making sure that the Service Account has one of the following roles
- Firebase Admin
- Firebase Dynamic Links Viewer
- Firebase Dynamic Links Admin
MSG,
default => <<<'MSG'
Failed to get statistics for dynamic link. Please inspect the
response for further details.
If the response type is not covered by the SDK, please create
a new issue in the SDK's GitHub repository and include the
HTTP Status code (`$response->getStatusCode()`) and the
message body (`$response->getBody()->getContents()`)
MSG,
};
return [
'code' => $code,
'message' => $message,
];
}
}

View File

@@ -0,0 +1,129 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\DynamicLink;
use JsonSerializable;
/**
* @phpstan-type IOSInfoShape array{
* iosBundleId?: non-empty-string,
* iosFallbackLink?: non-empty-string,
* iosCustomScheme?: non-empty-string,
* iosIpadFallbackLink?: non-empty-string,
* iosIpadBundleId?: non-empty-string,
* iosAppStoreId?: non-empty-string
* }
*/
final class IOSInfo implements JsonSerializable
{
/**
* @param IOSInfoShape $data
*/
private function __construct(private readonly array $data)
{
}
/**
* @param IOSInfoShape $data
*/
public static function fromArray(array $data): self
{
return new self($data);
}
public static function new(): self
{
return new self([]);
}
/**
* The bundle ID of the iOS app to use to open the link. The app must be connected to your project from the
* Overview page of the Firebase console. Required for the Dynamic Link to open an iOS app.
*
* @param non-empty-string $bundleId
*/
public function withBundleId(string $bundleId): self
{
$data = $this->data;
$data['iosBundleId'] = $bundleId;
return new self($data);
}
/**
* The link to open when the app isn't installed. Specify this to do something other than install your app from the
* App Store when the app isn't installed, such as open the mobile web version of the content, or display a
* promotional page for your app.
*
* @param non-empty-string $fallbackLink
*/
public function withFallbackLink(string $fallbackLink): self
{
$data = $this->data;
$data['iosFallbackLink'] = $fallbackLink;
return new self($data);
}
/**
* Your app's custom URL scheme, if defined to be something other than your app's bundle ID.
*
* @param non-empty-string $customScheme
*/
public function withCustomScheme(string $customScheme): self
{
$data = $this->data;
$data['iosCustomScheme'] = $customScheme;
return new self($data);
}
/**
* The link to open on iPads when the app isn't installed. Specify this to do something other than install your
* app from the App Store when the app isn't installed, such as open the web version of the content, or
* display a promotional page for your app.
*
* @param non-empty-string $ipadFallbackLink
*/
public function withIPadFallbackLink(string $ipadFallbackLink): self
{
$data = $this->data;
$data['iosIpadFallbackLink'] = $ipadFallbackLink;
return new self($data);
}
/**
* The bundle ID of the iOS app to use on iPads to open the link. The app must be connected to your project from
* the Overview page of the Firebase console.
*
* @param non-empty-string $iPadBundleId
*/
public function withIPadBundleId(string $iPadBundleId): self
{
$data = $this->data;
$data['iosIpadBundleId'] = $iPadBundleId;
return new self($data);
}
/**
* Your app's App Store ID, used to send users to the App Store when the app isn't installed.
*
* @param non-empty-string $appStoreId
*/
public function withAppStoreId(string $appStoreId): self
{
$data = $this->data;
$data['iosAppStoreId'] = $appStoreId;
return new self($data);
}
public function jsonSerialize(): array
{
return $this->data;
}
}

View File

@@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\DynamicLink;
use JsonSerializable;
/**
* @phpstan-type NavigationInfoShape array{
* enableForcedRedirect?: bool
* }
*/
final class NavigationInfo implements JsonSerializable
{
/**
* @param NavigationInfoShape $data
*/
private function __construct(private readonly array $data)
{
}
/**
* @param NavigationInfoShape $data
*/
public static function fromArray(array $data): self
{
return new self($data);
}
public static function new(): self
{
return new self([]);
}
/**
* If set, skip the app preview page when the Dynamic Link is opened, and instead redirect to the app
* or store. The app preview page (enabled by default) can more reliably send users to the most appropriate
* destination when they open Dynamic Links in apps; however, if you expect a Dynamic Link to be opened
* only in apps that can open Dynamic Links reliably without this page, you can disable it with this
* parameter. Note: the app preview page is only shown on iOS currently, but may eventually be
* shown on Android. This parameter will affect the behavior of the Dynamic Link on both
* platforms.
*/
public function withForcedRedirect(): self
{
$data = $this->data;
$data['enableForcedRedirect'] = true;
return new self($data);
}
/**
* @see withForcedRedirect()
*/
public function withoutForcedRedirect(): self
{
$data = $this->data;
unset($data['enableForcedRedirect']);
return new self($data);
}
public function jsonSerialize(): array
{
return $this->data;
}
}

View File

@@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\DynamicLink;
use JsonSerializable;
use Kreait\Firebase\Value\Url;
use Stringable;
/**
* @phpstan-type ShortenLongDynamicLinkShape array{
* longDynamicLink: non-empty-string,
* suffix: array{
* option: self::WITH*
* }
* }
*/
final class ShortenLongDynamicLink implements JsonSerializable
{
public const WITH_UNGUESSABLE_SUFFIX = 'UNGUESSABLE';
public const WITH_SHORT_SUFFIX = 'SHORT';
/**
* @param ShortenLongDynamicLinkShape $data
*/
private function __construct(private readonly array $data)
{
}
/**
* The long dynamic link that has been created as described in {@see https://firebase.google.com/docs/dynamic-links/create-manually}.
*/
public static function forLongDynamicLink(Stringable|string $url): self
{
return new self([
'longDynamicLink' => Url::fromString($url)->value,
'suffix' => ['option' => self::WITH_UNGUESSABLE_SUFFIX],
]);
}
/**
* @param ShortenLongDynamicLinkShape $data
*/
public static function fromArray(array $data): self
{
return new self($data);
}
public function withUnguessableSuffix(): self
{
$data = $this->data;
$data['suffix']['option'] = self::WITH_UNGUESSABLE_SUFFIX;
return new self($data);
}
public function withShortSuffix(): self
{
$data = $this->data;
$data['suffix']['option'] = self::WITH_SHORT_SUFFIX;
return new self($data);
}
public function jsonSerialize(): array
{
return $this->data;
}
}

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\DynamicLink\ShortenLongDynamicLink;
use Beste\Json;
use Kreait\Firebase\DynamicLink\ShortenLongDynamicLink;
use Kreait\Firebase\Exception\RuntimeException;
use Psr\Http\Message\ResponseInterface;
use UnexpectedValueException;
final class FailedToShortenLongDynamicLink extends RuntimeException
{
private ?ShortenLongDynamicLink $action = null;
private ?ResponseInterface $response = null;
public static function withActionAndResponse(ShortenLongDynamicLink $action, ResponseInterface $response): self
{
$fallbackMessage = 'Failed to shorten long dynamic link';
try {
$message = Json::decode((string) $response->getBody(), true)['error']['message'] ?? $fallbackMessage;
} catch (UnexpectedValueException) {
$message = $fallbackMessage;
}
$error = new self($message);
$error->action = $action;
$error->response = $response;
return $error;
}
public function action(): ?ShortenLongDynamicLink
{
return $this->action;
}
public function response(): ?ResponseInterface
{
return $this->response;
}
}

View File

@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\DynamicLink;
use JsonSerializable;
/**
* @phpstan-type SocialMetaTagInfoShape array{
* socialTitle?: non-empty-string,
* socialDescription?: non-empty-string,
* socialImageLink?: non-empty-string
* }
*/
final class SocialMetaTagInfo implements JsonSerializable
{
/**
* @param SocialMetaTagInfoShape $data
*/
private function __construct(private readonly array $data)
{
}
/**
* @param SocialMetaTagInfoShape $data
*/
public static function fromArray(array $data): self
{
return new self($data);
}
public static function new(): self
{
return new self([]);
}
/**
* The title to use when the Dynamic Link is shared in a social post.
*
* @param non-empty-string $title
*/
public function withTitle(string $title): self
{
$data = $this->data;
$data['socialTitle'] = $title;
return new self($data);
}
/**
* The description to use when the Dynamic Link is shared in a social post.
*
* @param non-empty-string $description
*/
public function withDescription(string $description): self
{
$data = $this->data;
$data['socialDescription'] = $description;
return new self($data);
}
/**
* The URL to an image related to this link.
*
* @param non-empty-string $imageLink
*/
public function withImageLink(string $imageLink): self
{
$data = $this->data;
$data['socialImageLink'] = $imageLink;
return new self($data);
}
public function jsonSerialize(): array
{
return $this->data;
}
}