···2233namespace SocialDept\AtpClient\Client\Requests\Ozone;
4455+use SocialDept\AtpClient\Attributes\RequiresScope;
56use SocialDept\AtpClient\Client\Requests\Request;
77+use SocialDept\AtpClient\Enums\Scope;
68use SocialDept\AtpClient\Http\Response;
79810class TeamRequestClient extends Request
···1012 /**
1113 * Get team member
1214 *
1515+ * @requires transition:generic (rpc:tools.ozone.team.getMember)
1616+ *
1317 * @see https://docs.bsky.app/docs/api/tools-ozone-team-list-members
1418 */
1919+ #[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:tools.ozone.team.getMember')]
1520 public function getTeamMember(string $did): Response
1621 {
1722 return $this->atp->client->get(
···2328 /**
2429 * List team members
2530 *
3131+ * @requires transition:generic (rpc:tools.ozone.team.listMembers)
3232+ *
2633 * @see https://docs.bsky.app/docs/api/tools-ozone-team-list-members
2734 */
3535+ #[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:tools.ozone.team.listMembers')]
2836 public function listTeamMembers(int $limit = 50, ?string $cursor = null): Response
2937 {
3038 return $this->atp->client->get(
···3644 /**
3745 * Add team member
3846 *
4747+ * @requires transition:generic (rpc:tools.ozone.team.addMember)
4848+ *
3949 * @see https://docs.bsky.app/docs/api/tools-ozone-team-add-member
4050 */
5151+ #[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:tools.ozone.team.addMember')]
4152 public function addTeamMember(string $did, string $role): Response
4253 {
4354 return $this->atp->client->post(
···48594960 /**
5061 * Update team member
6262+ *
6363+ * @requires transition:generic (rpc:tools.ozone.team.updateMember)
5164 *
5265 * @see https://docs.bsky.app/docs/api/tools-ozone-team-update-member
5366 */
6767+ #[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:tools.ozone.team.updateMember')]
5468 public function updateTeamMember(
5569 string $did,
5670 ?bool $disabled = null,
···6882 /**
6983 * Delete team member
7084 *
8585+ * @requires transition:generic (rpc:tools.ozone.team.deleteMember)
8686+ *
7187 * @see https://docs.bsky.app/docs/api/tools-ozone-team-delete-member
7288 */
8989+ #[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:tools.ozone.team.deleteMember')]
7390 public function deleteTeamMember(string $did): Response
7491 {
7592 return $this->atp->client->post(
+11
src/Contracts/HasAtpSession.php
···11+<?php
22+33+namespace SocialDept\AtpClient\Contracts;
44+55+interface HasAtpSession
66+{
77+ /**
88+ * Get the ATP DID associated with this model.
99+ */
1010+ public function getAtpDid(): ?string;
1111+}
···4455use SocialDept\AtpClient\Data\Credentials;
66use SocialDept\AtpClient\Data\DPoPKey;
77+use SocialDept\AtpClient\Enums\AuthType;
88+use SocialDept\AtpClient\Enums\Scope;
79810class Session
911{
···6163 public function hasScope(string $scope): bool
6264 {
6365 return in_array($scope, $this->credentials->scope, true);
6666+ }
6767+6868+ /**
6969+ * Check if the session has the given scope (alias for hasScope with Scope enum support).
7070+ */
7171+ public function can(string|Scope $scope): bool
7272+ {
7373+ $scopeValue = $scope instanceof Scope ? $scope->value : $scope;
7474+7575+ return $this->hasScope($scopeValue);
7676+ }
7777+7878+ /**
7979+ * Check if the session has any of the given scopes.
8080+ *
8181+ * @param array<string|Scope> $scopes
8282+ */
8383+ public function canAny(array $scopes): bool
8484+ {
8585+ foreach ($scopes as $scope) {
8686+ if ($this->can($scope)) {
8787+ return true;
8888+ }
8989+ }
9090+9191+ return false;
9292+ }
9393+9494+ /**
9595+ * Check if the session has all of the given scopes.
9696+ *
9797+ * @param array<string|Scope> $scopes
9898+ */
9999+ public function canAll(array $scopes): bool
100100+ {
101101+ foreach ($scopes as $scope) {
102102+ if (! $this->can($scope)) {
103103+ return false;
104104+ }
105105+ }
106106+107107+ return true;
108108+ }
109109+110110+ /**
111111+ * Check if the session does NOT have the given scope.
112112+ */
113113+ public function cannot(string|Scope $scope): bool
114114+ {
115115+ return ! $this->can($scope);
116116+ }
117117+118118+ public function authType(): AuthType
119119+ {
120120+ return $this->credentials->authType;
121121+ }
122122+123123+ public function isLegacy(): bool
124124+ {
125125+ return $this->credentials->authType === AuthType::Legacy;
64126 }
6512766128 public function withCredentials(Credentials $credentials): self
+23-21
src/Session/SessionManager.php
···88use SocialDept\AtpClient\Contracts\CredentialProvider;
99use SocialDept\AtpClient\Contracts\KeyStore;
1010use SocialDept\AtpClient\Data\AccessToken;
1111-use SocialDept\AtpClient\Events\OAuthTokenRefreshed;
1212-use SocialDept\AtpClient\Events\OAuthTokenRefreshing;
1111+use SocialDept\AtpClient\Events\TokenRefreshed;
1212+use SocialDept\AtpClient\Events\TokenRefreshing;
1313use SocialDept\AtpClient\Exceptions\AuthenticationException;
1414use SocialDept\AtpClient\Exceptions\HandleResolutionException;
1515use SocialDept\AtpClient\Exceptions\SessionExpiredException;
1616use SocialDept\AtpResolver\Facades\Resolver;
1717+use SocialDept\Resolver\Support\Identity;
17181819class SessionManager
1920{
···2829 ) {}
29303031 /**
3131- * Resolve a handle or DID to a DID.
3232+ * Resolve an actor (handle or DID) to a DID.
3233 *
3334 * @throws HandleResolutionException
3435 */
3535- protected function resolveToDid(string $handleOrDid): string
3636+ protected function resolveToDid(string $actor): string
3637 {
3738 // If already a DID, return as-is
3838- if (str_starts_with($handleOrDid, 'did:')) {
3939- return $handleOrDid;
3939+ if (Identity::isDid($actor)) {
4040+ return $actor;
4041 }
41424243 // Resolve handle to DID
4343- $did = Resolver::handleToDid($handleOrDid);
4444+ $did = Resolver::handleToDid($actor);
44454546 if (! $did) {
4646- throw new HandleResolutionException($handleOrDid);
4747+ throw new HandleResolutionException($actor);
4748 }
48494950 return $did;
5051 }
51525253 /**
5353- * Get or create session for handle or DID
5454+ * Get or create session for an actor.
5455 */
5555- public function session(string $handleOrDid): Session
5656+ public function session(string $actor): Session
5657 {
5757- $did = $this->resolveToDid($handleOrDid);
5858+ $did = $this->resolveToDid($actor);
58595960 if (! isset($this->sessions[$did])) {
6061 $this->sessions[$did] = $this->createSession($did);
···6465 }
65666667 /**
6767- * Ensure session is valid, refresh if needed
6868+ * Ensure session is valid, refresh if needed.
6869 */
6969- public function ensureValid(string $handleOrDid): Session
7070+ public function ensureValid(string $actor): Session
7071 {
7171- $session = $this->session($handleOrDid);
7272+ $session = $this->session($actor);
72737374 // Check if token needs refresh
7475 if ($session->expiresIn() < $this->refreshThreshold) {
···7980 }
80818182 /**
8282- * Create session from app password
8383+ * Create session from app password.
8384 */
8485 public function fromAppPassword(
8585- string $handleOrDid,
8686+ string $actor,
8687 string $password
8788 ): Session {
8888- $did = $this->resolveToDid($handleOrDid);
8989+ $did = $this->resolveToDid($actor);
8990 $pdsEndpoint = Resolver::resolvePds($did);
90919192 $response = Http::post($pdsEndpoint.'/xrpc/com.atproto.server.createSession', [
9292- 'identifier' => $handleOrDid,
9393+ 'identifier' => $actor,
9394 'password' => $password,
9495 ]);
9596···9798 throw new AuthenticationException('Login failed');
9899 }
99100100100- $token = AccessToken::fromResponse($response->json(), $handleOrDid, $pdsEndpoint);
101101+ $token = AccessToken::fromResponse($response->json(), $actor, $pdsEndpoint);
101102102103 // Store credentials using DID as key
103104 $this->credentials->storeCredentials($did, $token);
···138139 $did = $session->did();
139140140141 // Fire event before refresh (allows developers to invalidate old token)
141141- event(new OAuthTokenRefreshing($session));
142142+ event(new TokenRefreshing($session));
142143143144 $newToken = $this->refresher->refresh(
144145 refreshToken: $session->refreshToken(),
145146 pdsEndpoint: $session->pdsEndpoint(),
146147 dpopKey: $session->dpopKey(),
147148 handle: $session->handle(),
149149+ authType: $session->authType(),
148150 );
149151150152 // Update credentials (CRITICAL: refresh tokens are single-use)
151153 $this->credentials->updateCredentials($did, $newToken);
152154153155 // Fire event after successful refresh
154154- event(new OAuthTokenRefreshed($session, $newToken));
156156+ event(new TokenRefreshed($session, $newToken));
155157156158 // Update session
157159 $newCreds = $this->credentials->getCredentials($did);