From 357d6482f9439e76878295e18dd16408a3c0eb8c Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Thu, 9 Apr 2026 10:52:31 +0530 Subject: [PATCH 1/6] Remove realtime HTTP app dependency --- app/init/resources/request.php | 36 ++++++++++++++++++++-------------- app/realtime.php | 30 ++++++++++++---------------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/app/init/resources/request.php b/app/init/resources/request.php index 156e151501..64e02ece82 100644 --- a/app/init/resources/request.php +++ b/app/init/resources/request.php @@ -51,6 +51,7 @@ use Utopia\DI\Container; use Utopia\Domains\Domain; use Utopia\DSN\DSN; use Utopia\Http\Http; +use Utopia\Http\Router; use Utopia\Locale\Locale; use Utopia\Logger\Log; use Utopia\Pools\Group; @@ -120,6 +121,16 @@ return function (Container $container): void { return $locale; }); + $container->set('requestRoutePath', function (Request $request) { + $url = \parse_url($request->getURI(), PHP_URL_PATH); + $url = \is_string($url) ? ($url === '' ? '/' : $url) : '/'; + $method = $request->getMethod(); + $method = $method === Http::REQUEST_METHOD_HEAD ? Http::REQUEST_METHOD_GET : $method; + $route = Router::match($method, $url); + + return $route?->getPath() ?? $request->getURI(); + }, ['request']); + // Per-request queue resources (stateful, accumulate event data during request) $container->set('queueForMessaging', function (Publisher $publisher) { return new Messaging($publisher); @@ -625,7 +636,7 @@ return function (Container $container): void { return $user; }, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform', 'store', 'proofForToken', 'authorization']); - $container->set('project', function ($dbForPlatform, $request, $console, $authorization, Http $utopia) { + $container->set('project', function ($dbForPlatform, $request, $console, $authorization, string $requestRoutePath) { /** @var Appwrite\Utopia\Request $request */ /** @var Utopia\Database\Database $dbForPlatform */ /** @var Utopia\Database\Document $console */ @@ -639,14 +650,11 @@ return function (Container $container): void { // These endpoints moved from /v1/projects/:projectId/ to /v1/ // When accessed via the old alias path, extract projectId from the URI $deprecatedProjectPathPrefix = '/v1/projects/'; - $route = $utopia->match($request); - if (!empty($route)) { - $isDeprecatedAlias = \str_starts_with($request->getURI(), $deprecatedProjectPathPrefix) && - !\str_starts_with($route->getPath(), $deprecatedProjectPathPrefix); + $isDeprecatedAlias = \str_starts_with($request->getURI(), $deprecatedProjectPathPrefix) && + !\str_starts_with($requestRoutePath, $deprecatedProjectPathPrefix); - if ($isDeprecatedAlias) { - $projectId = \explode('/', $request->getURI(), 5)[3] ?? ''; - } + if ($isDeprecatedAlias) { + $projectId = \explode('/', $request->getURI(), 5)[3] ?? ''; } if (empty($projectId) || $projectId === 'console') { @@ -656,7 +664,7 @@ return function (Container $container): void { $project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $projectId)); return $project; - }, ['dbForPlatform', 'request', 'console', 'authorization', 'utopia']); + }, ['dbForPlatform', 'request', 'console', 'authorization', 'requestRoutePath']); $container->set('session', function (User $user, Store $store, Token $proofForToken) { if ($user->isEmpty()) { @@ -1123,20 +1131,18 @@ return function (Container $container): void { return $key; }, ['request', 'project', 'servers', 'dbForPlatform', 'authorization']); - $container->set('team', function (Document $project, Database $dbForPlatform, Http $utopia, Request $request, Authorization $authorization) { + $container->set('team', function (Document $project, Database $dbForPlatform, string $requestRoutePath, Request $request, Authorization $authorization) { $teamInternalId = ''; if ($project->getId() !== 'console') { $teamInternalId = $project->getAttribute('teamInternalId', ''); } else { - $route = $utopia->match($request); - $path = ! empty($route) ? $route->getPath() : $request->getURI(); $orgHeader = $request->getHeader('x-appwrite-organization', ''); - if (str_starts_with($path, '/v1/projects/:projectId')) { + if (str_starts_with($requestRoutePath, '/v1/projects/:projectId')) { $uri = $request->getURI(); $pid = explode('/', $uri)[3]; $p = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $pid)); $teamInternalId = $p->getAttribute('teamInternalId', ''); - } elseif ($path === '/v1/projects') { + } elseif ($requestRoutePath === '/v1/projects') { $teamId = $request->getParam('teamId', ''); if (empty($teamId)) { @@ -1164,7 +1170,7 @@ return function (Container $container): void { }); return $team; - }, ['project', 'dbForPlatform', 'utopia', 'request', 'authorization']); + }, ['project', 'dbForPlatform', 'requestRoutePath', 'request', 'authorization']); $container->set('previewHostname', function (Request $request, ?Key $apiKey) { $allowed = false; diff --git a/app/realtime.php b/app/realtime.php index 97ea7e32d5..12b7104d40 100644 --- a/app/realtime.php +++ b/app/realtime.php @@ -35,8 +35,6 @@ use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; use Utopia\DI\Container; use Utopia\DSN\DSN; -use Utopia\Http\Adapter\FPM\Server as HttpServer; -use Utopia\Http\Http; use Utopia\Logger\Log; use Utopia\Pools\Group; use Utopia\Registry\Registry; @@ -45,11 +43,11 @@ use Utopia\Telemetry\Adapter\None as NoTelemetry; use Utopia\WebSocket\Adapter; use Utopia\WebSocket\Server; -/** - * @var Registry $register - */ require_once __DIR__ . '/init.php'; +/** @var Registry $register */ +$register = $GLOBALS['register'] ?? throw new \RuntimeException('Registry not initialized'); + $registerRequestResources ??= require __DIR__ . '/init/resources/request.php'; Runtime::enableCoroutine(SWOOLE_HOOK_ALL); @@ -557,7 +555,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, $receivers = $realtime->getSubscribers($event); - if (Http::isDevelopment() && !empty($receivers)) { + if (System::getEnv('_APP_ENV', 'production') === 'development' && !empty($receivers)) { Console::log("[Debug][Worker {$workerId}] Receivers: " . count($receivers)); Console::log("[Debug][Worker {$workerId}] Connection IDs: " . json_encode(array_keys($receivers))); Console::log("[Debug][Worker {$workerId}] Matched: " . json_encode(array_values($receivers))); @@ -631,10 +629,6 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server, Console::info("Connection open (user: {$connection})"); $connectionContainer = new Container($container); - - $adapter = new HttpServer($connectionContainer); - $app = new Http($adapter, 'UTC'); - $connectionContainer->set('utopia', fn () => $app); $connectionContainer->set('request', fn () => $request); $connectionContainer->set('response', fn () => $response); @@ -646,8 +640,8 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server, try { /** @var Document $project */ - $project = $app->getResource('project'); - $authorization = $app->getResource('authorization'); + $project = $connectionContainer->get('project'); + $authorization = $connectionContainer->get('authorization'); /* * Project Check @@ -656,8 +650,8 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server, throw new Exception(Exception::REALTIME_POLICY_VIOLATION, 'Missing or unknown project ID'); } - $timelimit = $app->getResource('timelimit'); - $user = $app->getResource('user'); /** @var User $user */ + $timelimit = $connectionContainer->get('timelimit'); + $user = $connectionContainer->get('user'); /** @var User $user */ $logUser = $user; if ( @@ -702,7 +696,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server, * Skip this check for non-web platforms which are not required to send an origin header. */ $origin = $request->getOrigin(); - $originValidator = $app->getResource('originValidator'); + $originValidator = $connectionContainer->get('originValidator'); if (!empty($origin) && !$originValidator->isValid($origin) && $project->getId() !== 'console') { throw new Exception(Exception::REALTIME_POLICY_VIOLATION, $originValidator->getDescription()); @@ -789,7 +783,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server, // sanitize 0 && 5xx errors $realtimeViolation = $th instanceof AppwriteException && $th->getType() === AppwriteException::REALTIME_POLICY_VIOLATION; - if (($code === 0 || $code >= 500) && !$realtimeViolation && !Http::isDevelopment()) { + if (($code === 0 || $code >= 500) && !$realtimeViolation && System::getEnv('_APP_ENV', 'production') !== 'development') { $message = 'Error: Server Error'; } @@ -804,7 +798,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $server->send([$connection], json_encode($response)); $server->close($connection, $code); - if (Http::isDevelopment()) { + if (System::getEnv('_APP_ENV', 'production') === 'development') { Console::error('[Error] Connection Error'); Console::error('[Error] Code: ' . $response['data']['code']); Console::error('[Error] Message: ' . $response['data']['message']); @@ -988,7 +982,7 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re $message = $th->getMessage(); // sanitize 0 && 5xx errors - if (($code === 0 || $code >= 500) && !Http::isDevelopment()) { + if (($code === 0 || $code >= 500) && System::getEnv('_APP_ENV', 'production') !== 'development') { $message = 'Error: Server Error'; } From bf489ce13b956bd5aa8e07c2abcff16c9cac2327 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Thu, 9 Apr 2026 11:09:21 +0530 Subject: [PATCH 2/6] Fix requestRoutePath fallback --- app/init/resources/request.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/init/resources/request.php b/app/init/resources/request.php index 64e02ece82..7e63cd8a9e 100644 --- a/app/init/resources/request.php +++ b/app/init/resources/request.php @@ -128,7 +128,7 @@ return function (Container $container): void { $method = $method === Http::REQUEST_METHOD_HEAD ? Http::REQUEST_METHOD_GET : $method; $route = Router::match($method, $url); - return $route?->getPath() ?? $request->getURI(); + return $route?->getPath() ?? $url; }, ['request']); // Per-request queue resources (stateful, accumulate event data during request) From 2ca551123d3d272986640c46fd5cbdacce588b08 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Fri, 10 Apr 2026 09:25:00 +0530 Subject: [PATCH 3/6] use connection container --- app/init/resources/connection.php | 425 ++++++++++++++++++++++++++++++ app/init/resources/request.php | 36 ++- app/realtime.php | 7 +- 3 files changed, 443 insertions(+), 25 deletions(-) create mode 100644 app/init/resources/connection.php diff --git a/app/init/resources/connection.php b/app/init/resources/connection.php new file mode 100644 index 0000000000..43cf221b9c --- /dev/null +++ b/app/init/resources/connection.php @@ -0,0 +1,425 @@ +set('authorization', function () { + return new Authorization(); + }, []); + + $container->set('store', function (): Store { + return new Store(); + }, []); + + $container->set('proofForToken', function (): Token { + $token = new Token(); + $token->setHash(new Sha()); + + return $token; + }); + + $container->set('dbForPlatform', function (Group $pools, Cache $cache, Authorization $authorization) { + $adapter = new DatabasePool($pools->get('console')); + $database = new Database($adapter, $cache); + + $database + ->setDatabase(APP_DATABASE) + ->setAuthorization($authorization) + ->setNamespace('_console') + ->setMetadata('host', \gethostname()) + ->setMetadata('project', 'console') + ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API) + ->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES); + + $database->setDocumentType('users', User::class); + + return $database; + }, ['pools', 'cache', 'authorization']); + + $container->set('projectId', function (Request $request) { + $projectId = $request->getHeader('x-appwrite-project', ''); + + if (!empty($projectId)) { + return $projectId; + } + + $projectId = $request->getParam('project', ''); + + return \is_string($projectId) ? $projectId : ''; + }, ['request']); + + $container->set('project', function (Database $dbForPlatform, string $projectId, Document $console, Authorization $authorization) { + if (empty($projectId) || $projectId === 'console') { + return $console; + } + + return $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $projectId)); + }, ['dbForPlatform', 'projectId', 'console', 'authorization']); + + $container->set('mode', function (Request $request, Document $project, string $projectId) { + $mode = $request->getParam('mode', $request->getHeader('x-appwrite-mode', APP_MODE_DEFAULT)); + + if (!empty($projectId) && $project->getId() !== $projectId) { + $mode = APP_MODE_ADMIN; + } + + return $mode; + }, ['request', 'project', 'projectId']); + + $container->set('dbForProject', function (Group $pools, Database $dbForPlatform, Cache $cache, Document $project, Authorization $authorization) { + if ($project->isEmpty() || $project->getId() === 'console') { + return $dbForPlatform; + } + + $database = $project->getAttribute('database', ''); + if (empty($database)) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Project database is not configured'); + } + + try { + $dsn = new DSN($database); + } catch (\InvalidArgumentException) { + // TODO: Temporary until all projects are using shared tables + $dsn = new DSN('mysql://' . $database); + } + + $adapter = new DatabasePool($pools->get($dsn->getHost())); + $database = new Database($adapter, $cache); + + $database + ->setDatabase(APP_DATABASE) + ->setAuthorization($authorization) + ->setMetadata('host', \gethostname()) + ->setMetadata('project', $project->getId()) + ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API) + ->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES); + $database->setDocumentType('users', User::class); + + $sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', '')); + + if (\in_array($dsn->getHost(), $sharedTables)) { + $database + ->setSharedTables(true) + ->setTenant($project->getSequence()) + ->setNamespace($dsn->getParam('namespace')); + } else { + $database + ->setSharedTables(false) + ->setTenant(null) + ->setNamespace('_' . $project->getSequence()); + } + + return $database; + }, ['pools', 'dbForPlatform', 'cache', 'project', 'authorization']); + + $container->set('allowedHostnames', function (array $platform, Document $project, Document $rule, Document $devKey, Request $request) { + $allowed = [...($platform['hostnames'] ?? [])]; + + if (!$project->isEmpty() && $project->getId() !== 'console') { + $allowed = [...$allowed, ...Platform::getHostnames($project->getAttribute('platforms', []))]; + } + + if (!$devKey->isEmpty()) { + $allowed[] = $request->getHostname(); + } + + $originHostname = \parse_url($request->getOrigin(), PHP_URL_HOST); + $refererHostname = \parse_url($request->getReferer(), PHP_URL_HOST); + $hostname = $originHostname ?: $refererHostname; + + if ($request->getMethod() === 'OPTIONS' && !empty($hostname)) { + $allowed[] = $hostname; + } + + if (!$rule->isEmpty() && !empty($rule->getAttribute('domain', ''))) { + $allowed[] = $rule->getAttribute('domain', ''); + } + + if (!$devKey->isEmpty() && !empty($hostname)) { + $allowed[] = $hostname; + } + + return \array_unique($allowed); + }, ['platform', 'project', 'rule', 'devKey', 'request']); + + $container->set('allowedSchemes', function (array $platform, Document $project) { + $allowed = [...($platform['schemas'] ?? [])]; + + if (!$project->isEmpty() && $project->getId() !== 'console') { + $allowed[] = 'exp'; + $allowed[] = 'appwrite-callback-' . $project->getId(); + $allowed = [...$allowed, ...Platform::getSchemes($project->getAttribute('platforms', []))]; + } + + return \array_unique($allowed); + }, ['platform', 'project']); + + $container->set('rule', function (Request $request, Database $dbForPlatform, Document $project, Authorization $authorization) { + $domain = \parse_url($request->getOrigin(), PHP_URL_HOST); + + if (empty($domain)) { + $domain = \parse_url($request->getReferer(), PHP_URL_HOST); + } + + if (empty($domain)) { + return new Document(); + } + + $isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5'; + $rule = $authorization->skip(function () use ($dbForPlatform, $domain, $isMd5) { + if ($isMd5) { + return $dbForPlatform->getDocument('rules', md5($domain)); + } + + return $dbForPlatform->findOne('rules', [ + Query::equal('domain', [$domain]), + ]) ?? new Document(); + }); + + $permitsCurrentProject = $rule->getAttribute('projectInternalId', '') === $project->getSequence(); + + if (!$permitsCurrentProject && !$rule->isEmpty() && !empty($rule->getAttribute('projectId', ''))) { + $trustedProjects = []; + foreach (\explode(',', System::getEnv('_APP_CONSOLE_TRUSTED_PROJECTS', '')) as $trustedProject) { + if (empty($trustedProject)) { + continue; + } + $trustedProjects[] = $trustedProject; + } + + if (\in_array($rule->getAttribute('projectId', ''), $trustedProjects, true)) { + $permitsCurrentProject = true; + } + } + + if (!$permitsCurrentProject) { + return new Document(); + } + + return $rule; + }, ['request', 'dbForPlatform', 'project', 'authorization']); + + $container->set('devKey', function (Request $request, Document $project, array $servers, Database $dbForPlatform, Authorization $authorization) { + $devKey = $request->getHeader('x-appwrite-dev-key', $request->getParam('devKey', '')); + + $key = $project->find('secret', $devKey, 'devKeys'); + if (!$key) { + return new Document([]); + } + + $expire = $key->getAttribute('expire'); + if (!empty($expire) && $expire < DatabaseDateTime::formatTz(DatabaseDateTime::now())) { + return new Document([]); + } + + $accessedAt = $key->getAttribute('accessedAt', 0); + if (empty($accessedAt) || DatabaseDateTime::formatTz(DatabaseDateTime::addSeconds(new \DateTime(), -APP_KEY_ACCESS)) > $accessedAt) { + $key->setAttribute('accessedAt', DatabaseDateTime::now()); + $authorization->skip(fn () => $dbForPlatform->updateDocument('devKeys', $key->getId(), new Document([ + 'accessedAt' => $key->getAttribute('accessedAt'), + ]))); + $dbForPlatform->purgeCachedDocument('projects', $project->getId()); + } + + $sdkValidator = new WhiteList($servers, true); + $sdk = \strtolower($request->getHeader('x-sdk-name', 'UNKNOWN')); + + if ($sdk !== 'UNKNOWN' && $sdkValidator->isValid($sdk)) { + $sdks = $key->getAttribute('sdks', []); + + if (!\in_array($sdk, $sdks, true)) { + $sdks[] = $sdk; + $key->setAttribute('sdks', $sdks); + $key->setAttribute('accessedAt', DatabaseDateTime::now()); + + $key = $authorization->skip(fn () => $dbForPlatform->updateDocument('devKeys', $key->getId(), new Document([ + 'sdks' => $key->getAttribute('sdks'), + 'accessedAt' => $key->getAttribute('accessedAt'), + ]))); + $dbForPlatform->purgeCachedDocument('projects', $project->getId()); + } + } + + return $key; + }, ['request', 'project', 'servers', 'dbForPlatform', 'authorization']); + + $container->set('originValidator', function (Document $devKey, array $allowedHostnames, array $allowedSchemes) { + if (!$devKey->isEmpty()) { + return new URL(); + } + + return new Origin($allowedHostnames, $allowedSchemes); + }, ['devKey', 'allowedHostnames', 'allowedSchemes']); + + $container->set('user', function (string $mode, Document $project, Document $console, Request $request, Database $dbForProject, Database $dbForPlatform, Store $store, Token $proofForToken, Authorization $authorization) { + $authorization->setDefaultStatus(true); + + $store->setKey('a_session_' . $project->getId()); + + if ($mode === APP_MODE_ADMIN) { + $store->setKey('a_session_' . $console->getId()); + } + + $store->decode( + $request->getCookie( + $store->getKey(), + $request->getCookie($store->getKey() . '_legacy', '') + ) + ); + + if (empty($store->getProperty('id', '')) && empty($store->getProperty('secret', ''))) { + $sessionHeader = $request->getHeader('x-appwrite-session', ''); + + if (!empty($sessionHeader)) { + $store->decode($sessionHeader); + } + } + + if (empty($store->getProperty('id', '')) && empty($store->getProperty('secret', ''))) { + $fallback = \json_decode($request->getHeader('x-fallback-cookies', ''), true); + $store->decode((\is_array($fallback) && isset($fallback[$store->getKey()])) ? $fallback[$store->getKey()] : ''); + } + + $user = null; + if ($mode === APP_MODE_ADMIN) { + /** @var User $user */ + $user = $dbForPlatform->getDocument('users', $store->getProperty('id', '')); + } else { + if ($project->isEmpty()) { + $user = new User([]); + } elseif (!empty($store->getProperty('id', ''))) { + if ($project->getId() === 'console') { + /** @var User $user */ + $user = $dbForPlatform->getDocument('users', $store->getProperty('id', '')); + } else { + /** @var User $user */ + $user = $dbForProject->getDocument('users', $store->getProperty('id', '')); + } + } + } + + if ( + !$user + || $user->isEmpty() + || !$user->sessionVerify($store->getProperty('secret', ''), $proofForToken) + ) { + $user = new User([]); + } + + $authJWT = $request->getHeader('x-appwrite-jwt', ''); + if (!empty($authJWT) && !$project->isEmpty()) { + if (!$user->isEmpty()) { + throw new Exception(Exception::USER_JWT_AND_COOKIE_SET); + } + + $jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0); + try { + $payload = $jwt->decode($authJWT); + } catch (JWTException $error) { + throw new Exception(Exception::USER_JWT_INVALID, 'Failed to verify JWT. ' . $error->getMessage()); + } + + $jwtUserId = $payload['userId'] ?? ''; + if (!empty($jwtUserId)) { + if ($mode === APP_MODE_ADMIN) { + $user = $dbForPlatform->getDocument('users', $jwtUserId); + } else { + $user = $dbForProject->getDocument('users', $jwtUserId); + } + } + + $jwtSessionId = $payload['sessionId'] ?? ''; + if (!empty($jwtSessionId) && empty($user->find('$id', $jwtSessionId, 'sessions'))) { + $user = new User([]); + } + } + + $accountKey = $request->getHeader('x-appwrite-key', ''); + $accountKeyUserId = $request->getHeader('x-appwrite-user', ''); + if (!empty($accountKeyUserId) && !empty($accountKey)) { + if (!$user->isEmpty()) { + throw new Exception(Exception::USER_API_KEY_AND_SESSION_SET); + } + + $accountKeyUser = $dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->getDocument('users', $accountKeyUserId)); + if (!$accountKeyUser->isEmpty()) { + $key = $accountKeyUser->find( + key: 'secret', + find: $accountKey, + subject: 'keys', + ); + + if (!empty($key)) { + $expire = $key->getAttribute('expire'); + if (!empty($expire) && $expire < DatabaseDateTime::formatTz(DatabaseDateTime::now())) { + throw new Exception(Exception::ACCOUNT_KEY_EXPIRED); + } + + $user = $accountKeyUser; + } + } + } + + $impersonateUserId = $request->getHeader('x-appwrite-impersonate-user-id', ''); + $impersonateEmail = $request->getHeader('x-appwrite-impersonate-user-email', ''); + $impersonatePhone = $request->getHeader('x-appwrite-impersonate-user-phone', ''); + if (!$user->isEmpty() && $user->getAttribute('impersonator', false)) { + $userDb = ($mode === APP_MODE_ADMIN || $project->getId() === 'console') ? $dbForPlatform : $dbForProject; + $targetUser = null; + + if (!empty($impersonateUserId)) { + $targetUser = $userDb->getAuthorization()->skip(fn () => $userDb->getDocument('users', $impersonateUserId)); + } elseif (!empty($impersonateEmail)) { + $targetUser = $userDb->getAuthorization()->skip(fn () => $userDb->findOne('users', [ + Query::equal('email', [\strtolower($impersonateEmail)]), + ])); + } elseif (!empty($impersonatePhone)) { + $targetUser = $userDb->getAuthorization()->skip(fn () => $userDb->findOne('users', [ + Query::equal('phone', [$impersonatePhone]), + ])); + } + + if ($targetUser !== null && !$targetUser->isEmpty()) { + $impersonator = clone $user; + $user = clone $targetUser; + $user->setAttribute('impersonatorUserId', $impersonator->getId()); + $user->setAttribute('impersonatorUserInternalId', $impersonator->getSequence()); + $user->setAttribute('impersonatorUserName', $impersonator->getAttribute('name', '')); + $user->setAttribute('impersonatorUserEmail', $impersonator->getAttribute('email', '')); + $user->setAttribute('impersonatorAccessedAt', $impersonator->getAttribute('accessedAt', 0)); + } + } + + $dbForProject->setMetadata('user', $user->getId()); + $dbForPlatform->setMetadata('user', $user->getId()); + + return $user; + }, ['mode', 'project', 'console', 'request', 'dbForProject', 'dbForPlatform', 'store', 'proofForToken', 'authorization']); +}; diff --git a/app/init/resources/request.php b/app/init/resources/request.php index 7e63cd8a9e..156e151501 100644 --- a/app/init/resources/request.php +++ b/app/init/resources/request.php @@ -51,7 +51,6 @@ use Utopia\DI\Container; use Utopia\Domains\Domain; use Utopia\DSN\DSN; use Utopia\Http\Http; -use Utopia\Http\Router; use Utopia\Locale\Locale; use Utopia\Logger\Log; use Utopia\Pools\Group; @@ -121,16 +120,6 @@ return function (Container $container): void { return $locale; }); - $container->set('requestRoutePath', function (Request $request) { - $url = \parse_url($request->getURI(), PHP_URL_PATH); - $url = \is_string($url) ? ($url === '' ? '/' : $url) : '/'; - $method = $request->getMethod(); - $method = $method === Http::REQUEST_METHOD_HEAD ? Http::REQUEST_METHOD_GET : $method; - $route = Router::match($method, $url); - - return $route?->getPath() ?? $url; - }, ['request']); - // Per-request queue resources (stateful, accumulate event data during request) $container->set('queueForMessaging', function (Publisher $publisher) { return new Messaging($publisher); @@ -636,7 +625,7 @@ return function (Container $container): void { return $user; }, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform', 'store', 'proofForToken', 'authorization']); - $container->set('project', function ($dbForPlatform, $request, $console, $authorization, string $requestRoutePath) { + $container->set('project', function ($dbForPlatform, $request, $console, $authorization, Http $utopia) { /** @var Appwrite\Utopia\Request $request */ /** @var Utopia\Database\Database $dbForPlatform */ /** @var Utopia\Database\Document $console */ @@ -650,11 +639,14 @@ return function (Container $container): void { // These endpoints moved from /v1/projects/:projectId/ to /v1/ // When accessed via the old alias path, extract projectId from the URI $deprecatedProjectPathPrefix = '/v1/projects/'; - $isDeprecatedAlias = \str_starts_with($request->getURI(), $deprecatedProjectPathPrefix) && - !\str_starts_with($requestRoutePath, $deprecatedProjectPathPrefix); + $route = $utopia->match($request); + if (!empty($route)) { + $isDeprecatedAlias = \str_starts_with($request->getURI(), $deprecatedProjectPathPrefix) && + !\str_starts_with($route->getPath(), $deprecatedProjectPathPrefix); - if ($isDeprecatedAlias) { - $projectId = \explode('/', $request->getURI(), 5)[3] ?? ''; + if ($isDeprecatedAlias) { + $projectId = \explode('/', $request->getURI(), 5)[3] ?? ''; + } } if (empty($projectId) || $projectId === 'console') { @@ -664,7 +656,7 @@ return function (Container $container): void { $project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $projectId)); return $project; - }, ['dbForPlatform', 'request', 'console', 'authorization', 'requestRoutePath']); + }, ['dbForPlatform', 'request', 'console', 'authorization', 'utopia']); $container->set('session', function (User $user, Store $store, Token $proofForToken) { if ($user->isEmpty()) { @@ -1131,18 +1123,20 @@ return function (Container $container): void { return $key; }, ['request', 'project', 'servers', 'dbForPlatform', 'authorization']); - $container->set('team', function (Document $project, Database $dbForPlatform, string $requestRoutePath, Request $request, Authorization $authorization) { + $container->set('team', function (Document $project, Database $dbForPlatform, Http $utopia, Request $request, Authorization $authorization) { $teamInternalId = ''; if ($project->getId() !== 'console') { $teamInternalId = $project->getAttribute('teamInternalId', ''); } else { + $route = $utopia->match($request); + $path = ! empty($route) ? $route->getPath() : $request->getURI(); $orgHeader = $request->getHeader('x-appwrite-organization', ''); - if (str_starts_with($requestRoutePath, '/v1/projects/:projectId')) { + if (str_starts_with($path, '/v1/projects/:projectId')) { $uri = $request->getURI(); $pid = explode('/', $uri)[3]; $p = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $pid)); $teamInternalId = $p->getAttribute('teamInternalId', ''); - } elseif ($requestRoutePath === '/v1/projects') { + } elseif ($path === '/v1/projects') { $teamId = $request->getParam('teamId', ''); if (empty($teamId)) { @@ -1170,7 +1164,7 @@ return function (Container $container): void { }); return $team; - }, ['project', 'dbForPlatform', 'requestRoutePath', 'request', 'authorization']); + }, ['project', 'dbForPlatform', 'utopia', 'request', 'authorization']); $container->set('previewHostname', function (Request $request, ?Key $apiKey) { $allowed = false; diff --git a/app/realtime.php b/app/realtime.php index 12b7104d40..b4658db99d 100644 --- a/app/realtime.php +++ b/app/realtime.php @@ -48,7 +48,7 @@ require_once __DIR__ . '/init.php'; /** @var Registry $register */ $register = $GLOBALS['register'] ?? throw new \RuntimeException('Registry not initialized'); -$registerRequestResources ??= require __DIR__ . '/init/resources/request.php'; +$registerConnectionResources ??= require __DIR__ . '/init/resources/connection.php'; Runtime::enableCoroutine(SWOOLE_HOOK_ALL); @@ -621,7 +621,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, Console::error('Failed to restart pub/sub...'); }); -$server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $register, $stats, &$realtime, $registerRequestResources) { +$server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $register, $stats, &$realtime, $registerConnectionResources) { global $container; $request = new Request($request); $response = new Response(new SwooleResponse()); @@ -630,9 +630,8 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $connectionContainer = new Container($container); $connectionContainer->set('request', fn () => $request); - $connectionContainer->set('response', fn () => $response); - $registerRequestResources($connectionContainer); + $registerConnectionResources($connectionContainer); $project = null; $logUser = null; From 856046dc825cd204a80de2f5c82687bd794df961 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Fri, 10 Apr 2026 09:28:17 +0530 Subject: [PATCH 4/6] shrink the size --- app/init/resources/connection.php | 243 +++++++++++------------------- 1 file changed, 92 insertions(+), 151 deletions(-) diff --git a/app/init/resources/connection.php b/app/init/resources/connection.php index 43cf221b9c..0c1dbad923 100644 --- a/app/init/resources/connection.php +++ b/app/init/resources/connection.php @@ -10,60 +10,20 @@ use Appwrite\Utopia\Request; use Utopia\Auth\Hashes\Sha; use Utopia\Auth\Proofs\Token; use Utopia\Auth\Store; -use Utopia\Cache\Cache; -use Utopia\Database\Adapter\Pool as DatabasePool; -use Utopia\Database\Database; use Utopia\Database\DateTime as DatabaseDateTime; use Utopia\Database\Document; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; use Utopia\DI\Container; -use Utopia\DSN\DSN; -use Utopia\Pools\Group; use Utopia\System\System; use Utopia\Validator\URL; use Utopia\Validator\WhiteList; /** - * Register per-connection resources on the given container. - * These resources depend on the realtime connection context - * and must be fresh for each websocket connection. + * Register the minimal per-connection resources required by realtime. */ return function (Container $container): void { - $container->set('authorization', function () { - return new Authorization(); - }, []); - - $container->set('store', function (): Store { - return new Store(); - }, []); - - $container->set('proofForToken', function (): Token { - $token = new Token(); - $token->setHash(new Sha()); - - return $token; - }); - - $container->set('dbForPlatform', function (Group $pools, Cache $cache, Authorization $authorization) { - $adapter = new DatabasePool($pools->get('console')); - $database = new Database($adapter, $cache); - - $database - ->setDatabase(APP_DATABASE) - ->setAuthorization($authorization) - ->setNamespace('_console') - ->setMetadata('host', \gethostname()) - ->setMetadata('project', 'console') - ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API) - ->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES); - - $database->setDocumentType('users', User::class); - - return $database; - }, ['pools', 'cache', 'authorization']); - - $container->set('projectId', function (Request $request) { + $getProjectId = static function (Request $request): string { $projectId = $request->getHeader('x-appwrite-project', ''); if (!empty($projectId)) { @@ -73,115 +33,38 @@ return function (Container $container): void { $projectId = $request->getParam('project', ''); return \is_string($projectId) ? $projectId : ''; - }, ['request']); + }; - $container->set('project', function (Database $dbForPlatform, string $projectId, Document $console, Authorization $authorization) { - if (empty($projectId) || $projectId === 'console') { - return $console; - } - - return $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $projectId)); - }, ['dbForPlatform', 'projectId', 'console', 'authorization']); - - $container->set('mode', function (Request $request, Document $project, string $projectId) { + $getMode = static function (Request $request, Document $project) use ($getProjectId): string { $mode = $request->getParam('mode', $request->getHeader('x-appwrite-mode', APP_MODE_DEFAULT)); + $projectId = $getProjectId($request); if (!empty($projectId) && $project->getId() !== $projectId) { $mode = APP_MODE_ADMIN; } return $mode; - }, ['request', 'project', 'projectId']); + }; - $container->set('dbForProject', function (Group $pools, Database $dbForPlatform, Cache $cache, Document $project, Authorization $authorization) { - if ($project->isEmpty() || $project->getId() === 'console') { - return $dbForPlatform; - } - - $database = $project->getAttribute('database', ''); - if (empty($database)) { - throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Project database is not configured'); - } - - try { - $dsn = new DSN($database); - } catch (\InvalidArgumentException) { - // TODO: Temporary until all projects are using shared tables - $dsn = new DSN('mysql://' . $database); - } - - $adapter = new DatabasePool($pools->get($dsn->getHost())); - $database = new Database($adapter, $cache); - - $database - ->setDatabase(APP_DATABASE) - ->setAuthorization($authorization) - ->setMetadata('host', \gethostname()) - ->setMetadata('project', $project->getId()) - ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API) - ->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES); - $database->setDocumentType('users', User::class); - - $sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', '')); - - if (\in_array($dsn->getHost(), $sharedTables)) { - $database - ->setSharedTables(true) - ->setTenant($project->getSequence()) - ->setNamespace($dsn->getParam('namespace')); - } else { - $database - ->setSharedTables(false) - ->setTenant(null) - ->setNamespace('_' . $project->getSequence()); - } + $getDbForPlatform = static function (Authorization $authorization) { + $database = getConsoleDB(); + $database->setAuthorization($authorization); return $database; - }, ['pools', 'dbForPlatform', 'cache', 'project', 'authorization']); + }; - $container->set('allowedHostnames', function (array $platform, Document $project, Document $rule, Document $devKey, Request $request) { - $allowed = [...($platform['hostnames'] ?? [])]; - - if (!$project->isEmpty() && $project->getId() !== 'console') { - $allowed = [...$allowed, ...Platform::getHostnames($project->getAttribute('platforms', []))]; + $getDbForProject = static function (Document $project, Authorization $authorization) use ($getDbForPlatform) { + if ($project->isEmpty() || $project->getId() === 'console') { + return $getDbForPlatform($authorization); } - if (!$devKey->isEmpty()) { - $allowed[] = $request->getHostname(); - } + $database = getProjectDB($project); + $database->setAuthorization($authorization); - $originHostname = \parse_url($request->getOrigin(), PHP_URL_HOST); - $refererHostname = \parse_url($request->getReferer(), PHP_URL_HOST); - $hostname = $originHostname ?: $refererHostname; + return $database; + }; - if ($request->getMethod() === 'OPTIONS' && !empty($hostname)) { - $allowed[] = $hostname; - } - - if (!$rule->isEmpty() && !empty($rule->getAttribute('domain', ''))) { - $allowed[] = $rule->getAttribute('domain', ''); - } - - if (!$devKey->isEmpty() && !empty($hostname)) { - $allowed[] = $hostname; - } - - return \array_unique($allowed); - }, ['platform', 'project', 'rule', 'devKey', 'request']); - - $container->set('allowedSchemes', function (array $platform, Document $project) { - $allowed = [...($platform['schemas'] ?? [])]; - - if (!$project->isEmpty() && $project->getId() !== 'console') { - $allowed[] = 'exp'; - $allowed[] = 'appwrite-callback-' . $project->getId(); - $allowed = [...$allowed, ...Platform::getSchemes($project->getAttribute('platforms', []))]; - } - - return \array_unique($allowed); - }, ['platform', 'project']); - - $container->set('rule', function (Request $request, Database $dbForPlatform, Document $project, Authorization $authorization) { + $findRule = static function (Request $request, Document $project, Authorization $authorization) use ($getDbForPlatform): Document { $domain = \parse_url($request->getOrigin(), PHP_URL_HOST); if (empty($domain)) { @@ -192,7 +75,9 @@ return function (Container $container): void { return new Document(); } + $dbForPlatform = $getDbForPlatform($authorization); $isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5'; + $rule = $authorization->skip(function () use ($dbForPlatform, $domain, $isMd5) { if ($isMd5) { return $dbForPlatform->getDocument('rules', md5($domain)); @@ -211,6 +96,7 @@ return function (Container $container): void { if (empty($trustedProject)) { continue; } + $trustedProjects[] = $trustedProject; } @@ -224,12 +110,12 @@ return function (Container $container): void { } return $rule; - }, ['request', 'dbForPlatform', 'project', 'authorization']); + }; - $container->set('devKey', function (Request $request, Document $project, array $servers, Database $dbForPlatform, Authorization $authorization) { + $findDevKey = static function (Request $request, Document $project, array $servers, Authorization $authorization) use ($getDbForPlatform): Document { $devKey = $request->getHeader('x-appwrite-dev-key', $request->getParam('devKey', '')); - $key = $project->find('secret', $devKey, 'devKeys'); + if (!$key) { return new Document([]); } @@ -239,7 +125,9 @@ return function (Container $container): void { return new Document([]); } + $dbForPlatform = $getDbForPlatform($authorization); $accessedAt = $key->getAttribute('accessedAt', 0); + if (empty($accessedAt) || DatabaseDateTime::formatTz(DatabaseDateTime::addSeconds(new \DateTime(), -APP_KEY_ACCESS)) > $accessedAt) { $key->setAttribute('accessedAt', DatabaseDateTime::now()); $authorization->skip(fn () => $dbForPlatform->updateDocument('devKeys', $key->getId(), new Document([ @@ -268,21 +156,71 @@ return function (Container $container): void { } return $key; - }, ['request', 'project', 'servers', 'dbForPlatform', 'authorization']); + }; + + $container->set('authorization', function () { + return new Authorization(); + }, []); + + $container->set('project', function (Request $request, Document $console, Authorization $authorization) use ($getProjectId, $getDbForPlatform) { + $projectId = $getProjectId($request); + + if (empty($projectId) || $projectId === 'console') { + return $console; + } + + $dbForPlatform = $getDbForPlatform($authorization); + + return $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $projectId)); + }, ['request', 'console', 'authorization']); + + $container->set('originValidator', function (array $platform, Request $request, Document $project, array $servers, Authorization $authorization) use ($findDevKey, $findRule) { + $devKey = $findDevKey($request, $project, $servers, $authorization); - $container->set('originValidator', function (Document $devKey, array $allowedHostnames, array $allowedSchemes) { if (!$devKey->isEmpty()) { return new URL(); } - return new Origin($allowedHostnames, $allowedSchemes); - }, ['devKey', 'allowedHostnames', 'allowedSchemes']); + $allowedHostnames = [...($platform['hostnames'] ?? [])]; + if (!$project->isEmpty() && $project->getId() !== 'console') { + $allowedHostnames = [...$allowedHostnames, ...Platform::getHostnames($project->getAttribute('platforms', []))]; + } + + $rule = $findRule($request, $project, $authorization); + if (!$rule->isEmpty() && !empty($rule->getAttribute('domain', ''))) { + $allowedHostnames[] = $rule->getAttribute('domain', ''); + } + + $originHostname = \parse_url($request->getOrigin(), PHP_URL_HOST); + $refererHostname = \parse_url($request->getReferer(), PHP_URL_HOST); + $hostname = $originHostname ?: $refererHostname; + + if ($request->getMethod() === 'OPTIONS' && !empty($hostname)) { + $allowedHostnames[] = $hostname; + } + + $allowedSchemes = [...($platform['schemas'] ?? [])]; + if (!$project->isEmpty() && $project->getId() !== 'console') { + $allowedSchemes[] = 'exp'; + $allowedSchemes[] = 'appwrite-callback-' . $project->getId(); + $allowedSchemes = [...$allowedSchemes, ...Platform::getSchemes($project->getAttribute('platforms', []))]; + } + + return new Origin(\array_unique($allowedHostnames), \array_unique($allowedSchemes)); + }, ['platform', 'request', 'project', 'servers', 'authorization']); + + $container->set('user', function (Request $request, Document $project, Document $console, Authorization $authorization) use ($getMode, $getDbForPlatform, $getDbForProject) { + $mode = $getMode($request, $project); + $store = new Store(); + $proofForToken = new Token(); + $proofForToken->setHash(new Sha()); - $container->set('user', function (string $mode, Document $project, Document $console, Request $request, Database $dbForProject, Database $dbForPlatform, Store $store, Token $proofForToken, Authorization $authorization) { $authorization->setDefaultStatus(true); - $store->setKey('a_session_' . $project->getId()); + $dbForPlatform = $getDbForPlatform($authorization); + $dbForProject = $getDbForProject($project, $authorization); + $store->setKey('a_session_' . $project->getId()); if ($mode === APP_MODE_ADMIN) { $store->setKey('a_session_' . $console->getId()); } @@ -340,6 +278,7 @@ return function (Container $container): void { } $jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0); + try { $payload = $jwt->decode($authJWT); } catch (JWTException $error) { @@ -363,17 +302,18 @@ return function (Container $container): void { $accountKey = $request->getHeader('x-appwrite-key', ''); $accountKeyUserId = $request->getHeader('x-appwrite-user', ''); + if (!empty($accountKeyUserId) && !empty($accountKey)) { if (!$user->isEmpty()) { throw new Exception(Exception::USER_API_KEY_AND_SESSION_SET); } - $accountKeyUser = $dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->getDocument('users', $accountKeyUserId)); + $accountKeyUser = $authorization->skip(fn () => $dbForPlatform->getDocument('users', $accountKeyUserId)); if (!$accountKeyUser->isEmpty()) { $key = $accountKeyUser->find( key: 'secret', find: $accountKey, - subject: 'keys', + subject: 'keys' ); if (!empty($key)) { @@ -390,18 +330,19 @@ return function (Container $container): void { $impersonateUserId = $request->getHeader('x-appwrite-impersonate-user-id', ''); $impersonateEmail = $request->getHeader('x-appwrite-impersonate-user-email', ''); $impersonatePhone = $request->getHeader('x-appwrite-impersonate-user-phone', ''); + if (!$user->isEmpty() && $user->getAttribute('impersonator', false)) { $userDb = ($mode === APP_MODE_ADMIN || $project->getId() === 'console') ? $dbForPlatform : $dbForProject; $targetUser = null; if (!empty($impersonateUserId)) { - $targetUser = $userDb->getAuthorization()->skip(fn () => $userDb->getDocument('users', $impersonateUserId)); + $targetUser = $authorization->skip(fn () => $userDb->getDocument('users', $impersonateUserId)); } elseif (!empty($impersonateEmail)) { - $targetUser = $userDb->getAuthorization()->skip(fn () => $userDb->findOne('users', [ + $targetUser = $authorization->skip(fn () => $userDb->findOne('users', [ Query::equal('email', [\strtolower($impersonateEmail)]), ])); } elseif (!empty($impersonatePhone)) { - $targetUser = $userDb->getAuthorization()->skip(fn () => $userDb->findOne('users', [ + $targetUser = $authorization->skip(fn () => $userDb->findOne('users', [ Query::equal('phone', [$impersonatePhone]), ])); } @@ -417,9 +358,9 @@ return function (Container $container): void { } } - $dbForProject->setMetadata('user', $user->getId()); $dbForPlatform->setMetadata('user', $user->getId()); + $dbForProject->setMetadata('user', $user->getId()); return $user; - }, ['mode', 'project', 'console', 'request', 'dbForProject', 'dbForPlatform', 'store', 'proofForToken', 'authorization']); + }, ['request', 'project', 'console', 'authorization']); }; From a944c65660439da6e1f144d21a2c888c81207817 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Fri, 10 Apr 2026 09:43:32 +0530 Subject: [PATCH 5/6] refactor: move worker message resources --- app/init/{worker => resources}/message.php | 0 app/worker.php | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename app/init/{worker => resources}/message.php (100%) diff --git a/app/init/worker/message.php b/app/init/resources/message.php similarity index 100% rename from app/init/worker/message.php rename to app/init/resources/message.php diff --git a/app/worker.php b/app/worker.php index e55abb587c..ac74b6d1f0 100644 --- a/app/worker.php +++ b/app/worker.php @@ -1,8 +1,8 @@ Date: Fri, 10 Apr 2026 10:19:41 +0530 Subject: [PATCH 6/6] refactor: isolate realtime connection resources --- app/init/{resources => realtime}/connection.php | 0 app/init/{resources => worker}/message.php | 0 app/realtime.php | 2 +- app/worker.php | 2 +- 4 files changed, 2 insertions(+), 2 deletions(-) rename app/init/{resources => realtime}/connection.php (100%) rename app/init/{resources => worker}/message.php (100%) diff --git a/app/init/resources/connection.php b/app/init/realtime/connection.php similarity index 100% rename from app/init/resources/connection.php rename to app/init/realtime/connection.php diff --git a/app/init/resources/message.php b/app/init/worker/message.php similarity index 100% rename from app/init/resources/message.php rename to app/init/worker/message.php diff --git a/app/realtime.php b/app/realtime.php index b4658db99d..c8ebececa6 100644 --- a/app/realtime.php +++ b/app/realtime.php @@ -48,7 +48,7 @@ require_once __DIR__ . '/init.php'; /** @var Registry $register */ $register = $GLOBALS['register'] ?? throw new \RuntimeException('Registry not initialized'); -$registerConnectionResources ??= require __DIR__ . '/init/resources/connection.php'; +$registerConnectionResources ??= require __DIR__ . '/init/realtime/connection.php'; Runtime::enableCoroutine(SWOOLE_HOOK_ALL); diff --git a/app/worker.php b/app/worker.php index ac74b6d1f0..7cc34f397c 100644 --- a/app/worker.php +++ b/app/worker.php @@ -1,7 +1,7 @@