Laravel AT Protocol Client (alpha & unstable)
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

Set up project scaffolding with service provider, facade, and config

+209 -67
+37 -13
composer.json
··· 1 1 { 2 - "name": "social-dept/atp-client", 3 - "description": ":package_description", 2 + "name": "socialdept/atp-client", 3 + "description": "Type-safe AT Protocol HTTP client with OAuth 2.0 support for Laravel", 4 + "type": "library", 4 5 "license": "MIT", 6 + "keywords": [ 7 + "atproto", 8 + "bluesky", 9 + "at-protocol", 10 + "oauth", 11 + "xrpc", 12 + "laravel" 13 + ], 5 14 "authors": [ 6 15 { 7 - "name": "Author Name", 8 - "email": "author@email.com", 9 - "homepage": "http://author.com" 16 + "name": "SocialDept", 17 + "email": "hello@socialdept.com" 10 18 } 11 19 ], 12 - "homepage": "https://github.com/social-dept/atp-client", 13 - "keywords": ["Laravel", "AtpClient"], 14 20 "require": { 15 - "illuminate/support": "~9" 21 + "php": "^8.2", 22 + "illuminate/support": "^11.0|^12.0", 23 + "illuminate/http": "^11.0|^12.0", 24 + "guzzlehttp/guzzle": "^7.0", 25 + "web-token/jwt-core": "^3.0", 26 + "web-token/jwt-signature": "^3.0", 27 + "web-token/jwt-key-mgmt": "^3.0", 28 + "socialdept/atp-schema": "^0.2", 29 + "socialdept/atp-resolver": "^0.1" 16 30 }, 17 31 "require-dev": { 18 - "../../../vendor/phpunit/phpunit": "~9.0", 19 - "orchestra/testbench": "~7" 32 + "orchestra/testbench": "^9.0", 33 + "phpunit/phpunit": "^11.0", 34 + "friendsofphp/php-cs-fixer": "^3.89", 35 + "mockery/mockery": "^1.6", 36 + "pestphp/pest": "^3.0" 20 37 }, 21 38 "autoload": { 22 39 "psr-4": { ··· 25 42 }, 26 43 "autoload-dev": { 27 44 "psr-4": { 28 - "SocialDept\\AtpClient\\Tests\\": "tests" 45 + "SocialDept\\AtpClient\\Tests\\": "tests/" 29 46 } 47 + }, 48 + "scripts": { 49 + "test": "vendor/bin/pest", 50 + "test-coverage": "vendor/bin/pest --coverage", 51 + "format": "vendor/bin/php-cs-fixer fix" 30 52 }, 31 53 "extra": { 32 54 "laravel": { ··· 34 56 "SocialDept\\AtpClient\\AtpClientServiceProvider" 35 57 ], 36 58 "aliases": { 37 - "AtpClient": "SocialDept\\AtpClient\\Facades\\AtpClient" 59 + "Atp": "SocialDept\\AtpClient\\Facades\\Atp" 38 60 } 39 61 } 40 - } 62 + }, 63 + "minimum-stability": "dev", 64 + "prefer-stable": true 41 65 }
+65 -1
config/atp-client.php
··· 1 1 <?php 2 2 3 3 return [ 4 - // 4 + /* 5 + |-------------------------------------------------------------------------- 6 + | Client Metadata 7 + |-------------------------------------------------------------------------- 8 + | 9 + | OAuth client configuration. The metadata URL must be publicly accessible 10 + | and serve the client-metadata.json file. 11 + | 12 + */ 13 + 'client' => [ 14 + 'name' => env('ATP_CLIENT_NAME', config('app.name')), 15 + 'url' => env('ATP_CLIENT_URL', config('app.url')), 16 + 'metadata_url' => env('ATP_CLIENT_METADATA_URL'), 17 + 'redirect_uris' => [ 18 + env('ATP_CLIENT_REDIRECT_URI', config('app.url').'/auth/atp/callback'), 19 + ], 20 + 'scopes' => ['atproto', 'transition:generic'], 21 + ], 22 + 23 + /* 24 + |-------------------------------------------------------------------------- 25 + | Credential Provider 26 + |-------------------------------------------------------------------------- 27 + | 28 + | The credential provider handles storage and retrieval of OAuth tokens. 29 + | You can use the provided implementations or create your own. 30 + | 31 + */ 32 + 'credential_provider' => env( 33 + 'ATP_CREDENTIAL_PROVIDER', 34 + \SocialDept\AtpClient\Providers\ArrayCredentialProvider::class 35 + ), 36 + 37 + /* 38 + |-------------------------------------------------------------------------- 39 + | Session Settings 40 + |-------------------------------------------------------------------------- 41 + | 42 + | Configure session behavior including token refresh threshold and 43 + | DPoP key rotation interval. 44 + | 45 + */ 46 + 'session' => [ 47 + // Refresh token if expires within this many seconds 48 + 'refresh_threshold' => env('ATP_REFRESH_THRESHOLD', 300), 49 + 50 + // Rotate DPoP keys after this many seconds 51 + 'dpop_key_rotation' => env('ATP_DPOP_KEY_ROTATION', 86400), 52 + ], 53 + 54 + /* 55 + |-------------------------------------------------------------------------- 56 + | HTTP Settings 57 + |-------------------------------------------------------------------------- 58 + | 59 + | Configure HTTP client behavior for XRPC requests. 60 + | 61 + */ 62 + 'http' => [ 63 + 'timeout' => env('ATP_HTTP_TIMEOUT', 30), 64 + 'retry' => [ 65 + 'times' => env('ATP_HTTP_RETRY_TIMES', 3), 66 + 'sleep' => env('ATP_HTTP_RETRY_SLEEP', 100), 67 + ], 68 + ], 5 69 ];
+80 -53
src/AtpClientServiceProvider.php
··· 3 3 namespace SocialDept\AtpClient; 4 4 5 5 use Illuminate\Support\ServiceProvider; 6 + use SocialDept\AtpClient\Auth\DPoPKeyManager; 7 + use SocialDept\AtpClient\Auth\OAuthEngine; 8 + use SocialDept\AtpClient\Auth\TokenRefresher; 9 + use SocialDept\AtpClient\Client\AtpClient; 10 + use SocialDept\AtpClient\Contracts\CredentialProvider; 11 + use SocialDept\AtpClient\Contracts\KeyStore; 12 + use SocialDept\AtpClient\Providers\ArrayCredentialProvider; 13 + use SocialDept\AtpClient\Session\SessionManager; 14 + use SocialDept\AtpClient\Storage\EncryptedFileKeyStore; 6 15 7 16 class AtpClientServiceProvider extends ServiceProvider 8 17 { 9 18 /** 10 - * Perform post-registration booting of services. 11 - * 12 - * @return void 13 - */ 14 - public function boot(): void 15 - { 16 - // $this->loadTranslationsFrom(__DIR__.'/../resources/lang', 'social-dept'); 17 - // $this->loadViewsFrom(__DIR__.'/../resources/views', 'social-dept'); 18 - // $this->loadMigrationsFrom(__DIR__.'/../database/migrations'); 19 - // $this->loadRoutesFrom(__DIR__.'/routes.php'); 20 - 21 - // Publishing is only necessary when using the CLI. 22 - if ($this->app->runningInConsole()) { 23 - $this->bootForConsole(); 24 - } 25 - } 26 - 27 - /** 28 19 * Register any package services. 29 - * 30 - * @return void 31 20 */ 32 21 public function register(): void 33 22 { 34 23 $this->mergeConfigFrom(__DIR__.'/../config/atp-client.php', 'atp-client'); 35 24 36 - // Register the service the package provides. 37 - $this->app->singleton('atp-client', function ($app) { 38 - return new AtpClient; 25 + // Register contracts 26 + $this->app->singleton(CredentialProvider::class, function ($app) { 27 + $provider = config('atp-client.credential_provider'); 28 + 29 + return new $provider(); 30 + }); 31 + 32 + $this->app->singleton(KeyStore::class, function ($app) { 33 + return new EncryptedFileKeyStore( 34 + storage_path('app/atp-keys') 35 + ); 36 + }); 37 + 38 + // Register core services 39 + $this->app->singleton(DPoPKeyManager::class); 40 + $this->app->singleton(TokenRefresher::class); 41 + $this->app->singleton(SessionManager::class); 42 + $this->app->singleton(OAuthEngine::class); 43 + 44 + // Register main client facade accessor 45 + $this->app->bind('atp-client', function ($app) { 46 + return new class($app) 47 + { 48 + protected $app; 49 + 50 + protected ?CredentialProvider $defaultProvider = null; 51 + 52 + public function __construct($app) 53 + { 54 + $this->app = $app; 55 + } 56 + 57 + public function as(string $identifier): AtpClient 58 + { 59 + return new AtpClient( 60 + $this->app->make(SessionManager::class), 61 + $this->app->make('http'), 62 + $identifier 63 + ); 64 + } 65 + 66 + public function login(string $identifier, string $password): AtpClient 67 + { 68 + $session = $this->app->make(SessionManager::class) 69 + ->fromAppPassword($identifier, $password); 70 + 71 + return $this->as($identifier); 72 + } 73 + 74 + public function oauth(): OAuthEngine 75 + { 76 + return $this->app->make(OAuthEngine::class); 77 + } 78 + 79 + public function setDefaultProvider(CredentialProvider $provider): void 80 + { 81 + $this->defaultProvider = $provider; 82 + $this->app->instance(CredentialProvider::class, $provider); 83 + } 84 + }; 39 85 }); 40 86 } 41 87 42 88 /** 43 - * Get the services provided by the provider. 44 - * 45 - * @return array 89 + * Perform post-registration booting of services. 46 90 */ 47 - public function provides() 91 + public function boot(): void 48 92 { 49 - return ['atp-client']; 93 + if ($this->app->runningInConsole()) { 94 + $this->publishes([ 95 + __DIR__.'/../config/atp-client.php' => config_path('atp-client.php'), 96 + ], 'atp-client-config'); 97 + } 50 98 } 51 99 52 100 /** 53 - * Console-specific booting. 101 + * Get the services provided by the provider. 54 102 * 55 - * @return void 103 + * @return array<string> 56 104 */ 57 - protected function bootForConsole(): void 105 + public function provides(): array 58 106 { 59 - // Publishing the configuration file. 60 - $this->publishes([ 61 - __DIR__.'/../config/atp-client.php' => config_path('atp-client.php'), 62 - ], 'atp-client.config'); 63 - 64 - // Publishing the views. 65 - /*$this->publishes([ 66 - __DIR__.'/../resources/views' => base_path('resources/views/vendor/social-dept'), 67 - ], 'atp-client.views');*/ 68 - 69 - // Publishing assets. 70 - /*$this->publishes([ 71 - __DIR__.'/../resources/assets' => public_path('vendor/social-dept'), 72 - ], 'atp-client.assets');*/ 73 - 74 - // Publishing the translation files. 75 - /*$this->publishes([ 76 - __DIR__.'/../resources/lang' => resource_path('lang/vendor/social-dept'), 77 - ], 'atp-client.lang');*/ 78 - 79 - // Registering package commands. 80 - // $this->commands([]); 107 + return ['atp-client']; 81 108 } 82 109 }
+27
src/Facades/Atp.php
··· 1 + <?php 2 + 3 + namespace SocialDept\AtpClient\Facades; 4 + 5 + use Illuminate\Support\Facades\Facade; 6 + use SocialDept\AtpClient\Auth\OAuthEngine; 7 + use SocialDept\AtpClient\Client\AtpClient; 8 + use SocialDept\AtpClient\Contracts\CredentialProvider; 9 + 10 + /** 11 + * @method static AtpClient as(string $identifier) 12 + * @method static AtpClient login(string $identifier, string $password) 13 + * @method static OAuthEngine oauth() 14 + * @method static void setDefaultProvider(CredentialProvider $provider) 15 + * 16 + * @see \SocialDept\AtpClient\AtpClientServiceProvider 17 + */ 18 + class Atp extends Facade 19 + { 20 + /** 21 + * Get the registered name of the component. 22 + */ 23 + protected static function getFacadeAccessor(): string 24 + { 25 + return 'atp-client'; 26 + } 27 + }