diff --git a/docs b/docs index a0aae4d1e..07535f4de 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit a0aae4d1e264c9b56469a47d67780b7c345c4672 +Subproject commit 07535f4de8d8ee6b845ef36ff572bbc2ff1d6fe5 diff --git a/phpunit.xml b/phpunit.xml index edca29a03..43deaaebc 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -9,7 +9,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="true"> +> src diff --git a/src/API.php b/src/API.php index 70b60cd4f..d57a8ef41 100644 --- a/src/API.php +++ b/src/API.php @@ -101,6 +101,12 @@ final class API extends AbstractAPI * @var int */ public const LOGGED_IN = 3; + /** + * We're logged out, the session will be deleted ASAP. + * + * @var int + */ + public const LOGGED_OUT = 4; /** * This peer is a user. * diff --git a/src/APIWrapper.php b/src/APIWrapper.php index a18a2fd0a..2c05f0552 100644 --- a/src/APIWrapper.php +++ b/src/APIWrapper.php @@ -98,6 +98,10 @@ final class APIWrapper $this->API->waitForInit(); $API = $this->API; + if ($API->authorized === API::LOGGED_OUT) { + return false; + } + $this->session->serialize( $API->serializeSession($this), $this->session->getSessionPath(), diff --git a/src/InternalDoc.php b/src/InternalDoc.php index 37f845995..3885c6428 100644 --- a/src/InternalDoc.php +++ b/src/InternalDoc.php @@ -1269,6 +1269,11 @@ abstract class InternalDoc { $this->wrapper->getAPI()->logger($param, $level, $file); } + + public function logout(): void + { + $this->wrapper->getAPI()->logout(); + } /** * Start MadelineProto's update handling loop, or run the provided async callable. * diff --git a/src/Ipc/Client.php b/src/Ipc/Client.php index 742edd027..18ee32a91 100644 --- a/src/Ipc/Client.php +++ b/src/Ipc/Client.php @@ -141,6 +141,11 @@ final class Client extends ClientAbstract return true; } + public function getLogger(): Logger + { + return $this->logger; + } + /** @internal */ public function getQrLoginCancellation(): Cancellation { diff --git a/src/Ipc/Runner/entry.php b/src/Ipc/Runner/entry.php index c4a6122a9..b70e2abb3 100644 --- a/src/Ipc/Runner/entry.php +++ b/src/Ipc/Runner/entry.php @@ -137,7 +137,10 @@ use Webmozart\Assert\Assert; Logger::log("$e", Logger::FATAL_ERROR); Logger::log('Got exception in IPC server, exiting...', Logger::FATAL_ERROR); $ipc = $session->getIpcState(); - if (!($ipc && $ipc->getStartupId() === $runnerId && !$ipc->getException())) { + if (!($ipc && $ipc->getStartupId() === $runnerId && !$ipc->getException()) + && $API + && $API->getAuthorization() !== API::LOGGED_OUT + ) { Logger::log('Reporting error!'); $session->storeIpcState(new IpcState($runnerId, $e)); Logger::log('Reported error!'); diff --git a/src/MTProto.php b/src/MTProto.php index 08282fd7c..feb58a326 100644 --- a/src/MTProto.php +++ b/src/MTProto.php @@ -475,6 +475,9 @@ final class MTProto implements TLCallback, LoggerGetter if (self::$references) { Logger::log('Prompting final serialization (SHUTDOWN)...'); foreach (self::$references as $instance) { + if ($instance->authorized === API::LOGGED_OUT) { + continue; + } $instance->wrapper->serialize(); } Logger::log('Done final serialization (SHUTDOWN)!'); @@ -1122,6 +1125,9 @@ final class MTProto implements TLCallback, LoggerGetter } } $this->logger->logger('Unreferenced instance'); + if ($this->authorized === API::LOGGED_OUT) { + $this->wrapper->getSession()->delete(); + } } /** * Destructor. diff --git a/src/MTProtoSession/ResponseHandler.php b/src/MTProtoSession/ResponseHandler.php index c8a4f6041..c10d09d21 100644 --- a/src/MTProtoSession/ResponseHandler.php +++ b/src/MTProtoSession/ResponseHandler.php @@ -326,6 +326,7 @@ trait ResponseHandler $phone = isset($this->API->authorization['user']['phone']) ? '+' . $this->API->authorization['user']['phone'] : '???'; $this->logger->logger(\sprintf(Lang::$current_lang['account_banned'], $phone), Logger::FATAL_ERROR); } + $this->API->logout(); return fn () => new RPCErrorException($response['error_message'], $response['error_code'], $request->getConstructor()); case 'AUTH_KEY_UNREGISTERED': case 'AUTH_KEY_INVALID': @@ -345,6 +346,7 @@ trait ResponseHandler $this->logger->logger('Permanent auth key was main authorized key, logging out...', Logger::FATAL_ERROR); $phone = isset($this->API->authorization['user']['phone']) ? '+' . $this->API->authorization['user']['phone'] : 'you are currently using'; $this->logger->logger(\sprintf(Lang::$current_lang['account_banned'], $phone), Logger::FATAL_ERROR); + $this->API->logout(); return fn () => new RPCErrorException($response['error_message'], $response['error_code'], $request->getConstructor()); } EventLoop::queue(function () use ($request): void { diff --git a/src/SessionPaths.php b/src/SessionPaths.php index fbfe4c1bd..95e2394fe 100644 --- a/src/SessionPaths.php +++ b/src/SessionPaths.php @@ -21,6 +21,8 @@ declare(strict_types=1); namespace danog\MadelineProto; use danog\MadelineProto\Ipc\IpcState; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; use const LOCK_EX; use const LOCK_SH; @@ -29,10 +31,12 @@ use const PHP_MINOR_VERSION; use const PHP_VERSION; use function Amp\File\createDirectory; +use function Amp\File\deleteDirectory; use function Amp\File\deleteFile; use function Amp\File\exists; use function Amp\File\isDirectory; use function Amp\File\isFile; +use function Amp\File\listFiles; use function Amp\File\move; use function Amp\File\openFile; use function Amp\File\write; @@ -109,6 +113,21 @@ final class SessionPaths } } } + /** + * Deletes session. + */ + public function delete(): void + { + if (file_exists($this->sessionDirectoryPath)) { + foreach (scandir($this->sessionDirectoryPath) as $f) { + if ($f === '.' || $f === '..') { + continue; + } + unlink($this->sessionDirectoryPath.DIRECTORY_SEPARATOR.$f); + } + rmdir($this->sessionDirectoryPath); + } + } /** * Serialize object to file. */ diff --git a/src/Wrappers/Login.php b/src/Wrappers/Login.php index b9a26875b..ae65b66c8 100644 --- a/src/Wrappers/Login.php +++ b/src/Wrappers/Login.php @@ -24,15 +24,19 @@ use Amp\Cancellation; use Amp\CancelledException; use Amp\DeferredCancellation; use Amp\DeferredFuture; +use Amp\SignalException; use AssertionError; use danog\MadelineProto\API; use danog\MadelineProto\Exception; use danog\MadelineProto\Lang; use danog\MadelineProto\Logger; +use danog\MadelineProto\LogoutException; +use danog\MadelineProto\Magic; use danog\MadelineProto\MTProto\PermAuthKey; use danog\MadelineProto\MTProtoTools\PasswordCalculator; use danog\MadelineProto\RPCErrorException; use danog\MadelineProto\Settings; +use danog\MadelineProto\Shutdown; use danog\MadelineProto\TL\Types\LoginQrCode; use danog\MadelineProto\Tools; @@ -142,6 +146,19 @@ trait Login $c->cancel(); return $c->getCancellation(); } + public function logout(): void + { + if ($this->authorized === API::LOGGED_IN) { + $this->authorized = API::LOGGED_OUT; + $this->methodCallAsyncRead('auth.logOut'); + } + $this->authorized = API::LOGGED_OUT; + if ($this->hasEventHandler()) { + $this->stop(); + } else { + $this->ipcServer?->stop(); + } + } /** @internal */ public function waitQrLogin(): void { diff --git a/tests/danog/MadelineProto/DataCenterTest.php b/tests/danog/MadelineProto/DataCenterTest.php index ce2dd91a7..fd03c5edd 100644 --- a/tests/danog/MadelineProto/DataCenterTest.php +++ b/tests/danog/MadelineProto/DataCenterTest.php @@ -5,8 +5,8 @@ declare(strict_types=1); namespace danog\MadelineProto\Test; use Amp\PHPUnit\AsyncTestCase; +use danog\MadelineProto\API; use danog\MadelineProto\Logger; -use danog\MadelineProto\MTProto; use danog\MadelineProto\Settings; use danog\MadelineProto\Stream\MTProtoTransport\AbridgedStream; use danog\MadelineProto\Stream\MTProtoTransport\FullStream; @@ -36,8 +36,6 @@ final class DataCenterTest extends AsyncTestCase */ public function testCanUseProtocol(string $transport, bool $obfuscated, string $protocol, bool $test_mode, bool $ipv6): void { - $this->markTestSkipped(); - return;/* $settings = new Settings; $settings->getAppInfo() ->setApiHash(\getenv('API_HASH')) @@ -54,11 +52,17 @@ final class DataCenterTest extends AsyncTestCase ->setTransport($transport) ->setRetry(false) ->setTimeout(10); - $API = new MTProto($settings); - $API->getLogger()->logger("Testing protocol $protocol using transport $transport, ".($obfuscated ? 'obfuscated ' : 'not obfuscated ').($test_mode ? 'test DC ' : 'main DC ').($ipv6 ? 'IPv6 ' : 'IPv4 ')); - $ping = \random_bytes(8); - $this->assertEquals($ping, $API->methodCallAsyncRead('ping', ['ping_id' => $ping])['ping_id']);*/ + // Init session + $API = new API(\sys_get_temp_dir()."/{$transport}_{$obfuscated}_{$protocol}_{$test_mode}_$ipv6", $settings); + unset($API); + + // Fork + $API = new API(\sys_get_temp_dir()."/{$transport}_{$obfuscated}_{$protocol}_{$test_mode}_$ipv6", $settings); + $API->logger("Testing protocol $protocol using transport $transport, ".($obfuscated ? 'obfuscated ' : 'not obfuscated ').($test_mode ? 'test DC ' : 'main DC ').($ipv6 ? 'IPv6 ' : 'IPv4 ')); + + $this->assertIsArray($API->help->getConfig()); + $API->logout(); } public function protocolProvider(): Generator