···11+<?php
22+33+namespace SocialDept\AtpClient\Contracts;
44+55+use SocialDept\AtpClient\Data\AccessToken;
66+use SocialDept\AtpClient\Data\Credentials;
77+88+interface CredentialProvider
99+{
1010+ /**
1111+ * Get credentials for the given identifier
1212+ */
1313+ public function getCredentials(string $identifier): ?Credentials;
1414+1515+ /**
1616+ * Store new credentials (initial OAuth or app password login)
1717+ */
1818+ public function storeCredentials(string $identifier, AccessToken $token): void;
1919+2020+ /**
2121+ * Update credentials after token refresh
2222+ * CRITICAL: Refresh tokens are single-use!
2323+ */
2424+ public function updateCredentials(string $identifier, AccessToken $token): void;
2525+2626+ /**
2727+ * Remove credentials
2828+ */
2929+ public function removeCredentials(string $identifier): void;
3030+}
+28
src/Contracts/KeyStore.php
···11+<?php
22+33+namespace SocialDept\AtpClient\Contracts;
44+55+use SocialDept\AtpClient\Data\DPoPKey;
66+77+interface KeyStore
88+{
99+ /**
1010+ * Store DPoP key for session
1111+ */
1212+ public function store(string $sessionId, DPoPKey $key): void;
1313+1414+ /**
1515+ * Retrieve DPoP key
1616+ */
1717+ public function get(string $sessionId): ?DPoPKey;
1818+1919+ /**
2020+ * Delete DPoP key
2121+ */
2222+ public function delete(string $sessionId): void;
2323+2424+ /**
2525+ * Check if key exists
2626+ */
2727+ public function exists(string $sessionId): bool;
2828+}
+16
src/Contracts/Recordable.php
···11+<?php
22+33+namespace SocialDept\AtpClient\Contracts;
44+55+interface Recordable
66+{
77+ /**
88+ * Convert record to array for XRPC
99+ */
1010+ public function toArray(): array;
1111+1212+ /**
1313+ * Get the record type (lexicon NSID)
1414+ */
1515+ public function getType(): string;
1616+}
+25
src/Data/AccessToken.php
···11+<?php
22+33+namespace SocialDept\AtpClient\Data;
44+55+class AccessToken
66+{
77+ public function __construct(
88+ public readonly string $accessJwt,
99+ public readonly string $refreshJwt,
1010+ public readonly string $did,
1111+ public readonly \DateTimeInterface $expiresAt,
1212+ public readonly ?string $handle = null,
1313+ ) {}
1414+1515+ public static function fromResponse(array $data): self
1616+ {
1717+ return new self(
1818+ accessJwt: $data['accessJwt'],
1919+ refreshJwt: $data['refreshJwt'],
2020+ did: $data['did'],
2121+ expiresAt: now()->addSeconds($data['expiresIn'] ?? 300),
2222+ handle: $data['handle'] ?? null,
2323+ );
2424+ }
2525+}
+24
src/Data/Credentials.php
···11+<?php
22+33+namespace SocialDept\AtpClient\Data;
44+55+class Credentials
66+{
77+ public function __construct(
88+ public readonly string $identifier,
99+ public readonly string $did,
1010+ public readonly string $accessToken,
1111+ public readonly string $refreshToken,
1212+ public readonly \DateTimeInterface $expiresAt,
1313+ ) {}
1414+1515+ public function isExpired(): bool
1616+ {
1717+ return now()->isAfter($this->expiresAt);
1818+ }
1919+2020+ public function expiresIn(): int
2121+ {
2222+ return now()->diffInSeconds($this->expiresAt, false);
2323+ }
2424+}
+24
src/Data/DPoPKey.php
···11+<?php
22+33+namespace SocialDept\AtpClient\Data;
44+55+use Jose\Component\Core\JWK;
66+77+class DPoPKey
88+{
99+ public function __construct(
1010+ public readonly JWK $privateKey,
1111+ public readonly JWK $publicKey,
1212+ public readonly string $keyId,
1313+ ) {}
1414+1515+ public function getPublicJwk(): array
1616+ {
1717+ return $this->publicKey->jsonSerialize();
1818+ }
1919+2020+ public function getPrivateJwk(): array
2121+ {
2222+ return $this->privateKey->jsonSerialize();
2323+ }
2424+}
+27
src/Data/StrongRef.php
···11+<?php
22+33+namespace SocialDept\AtpClient\Data;
44+55+class StrongRef
66+{
77+ public function __construct(
88+ public readonly string $uri,
99+ public readonly string $cid,
1010+ ) {}
1111+1212+ public static function fromResponse(array $data): self
1313+ {
1414+ return new self(
1515+ uri: $data['uri'],
1616+ cid: $data['cid'],
1717+ );
1818+ }
1919+2020+ public function toArray(): array
2121+ {
2222+ return [
2323+ 'uri' => $this->uri,
2424+ 'cid' => $this->cid,
2525+ ];
2626+ }
2727+}