Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
d84474d
added presence api update
ArnabChatterjee20k Apr 29, 2026
f317b17
Add support for customizable additional properties key in model templ…
ArnabChatterjee20k Apr 30, 2026
dcd41a7
added presence api tests and mock server
ArnabChatterjee20k May 7, 2026
7a73a9d
Refactor WebSocket server integration and update dependencies
ArnabChatterjee20k May 8, 2026
5c16877
Merge remote-tracking branch 'origin/master' into presence-api
ArnabChatterjee20k May 8, 2026
f7f47ab
updated mock server
ArnabChatterjee20k May 8, 2026
fec309e
added log for presence printing
ArnabChatterjee20k May 11, 2026
71d628f
added debugging logs
ArnabChatterjee20k May 11, 2026
b4fda07
addded logs
ArnabChatterjee20k May 11, 2026
0fded05
separated realtime and http mock server
ArnabChatterjee20k May 11, 2026
19d4553
fixed model issue
ArnabChatterjee20k May 11, 2026
45a27b7
refactor: change createPresence to fire-and-forget across all platforms
ArnabChatterjee20k May 11, 2026
788f3fc
feat: implement fire-and-forget presence upsert with state tracking a…
ArnabChatterjee20k May 11, 2026
1ba7700
reverted http
ArnabChatterjee20k May 11, 2026
7be5b1d
removed debug statement from flutter
ArnabChatterjee20k May 11, 2026
96ac466
updated web
ArnabChatterjee20k May 11, 2026
385f8f4
refactor: rename lastPresenceData to pendingPresence and update relat…
ArnabChatterjee20k May 11, 2026
cac8129
removed realtime
ArnabChatterjee20k May 11, 2026
ea56197
http to use the utopia websocket
ArnabChatterjee20k May 11, 2026
396ac39
fxed swift tests
ArnabChatterjee20k May 11, 2026
26a0b1d
refactor: rename createPresence to upsertPresence across multiple lan…
ArnabChatterjee20k May 11, 2026
d0bb398
fix: update WebSocket endpoint to mock API for testing across multipl…
ArnabChatterjee20k May 12, 2026
d298fe4
fix: update client setup and improve subscription listener handling i…
ArnabChatterjee20k May 13, 2026
6f27598
fix: add utopia-php/query dependency and enhance query evaluation in …
ArnabChatterjee20k May 13, 2026
473be0d
fix: reorder disconnect log statements and update realtime presence t…
ArnabChatterjee20k May 13, 2026
df480bf
Merge remote-tracking branch 'origin/master' into presence-api
ArnabChatterjee20k May 13, 2026
ede9c2d
made the presenceId as required to make it consistent with the rest api
ArnabChatterjee20k May 14, 2026
c35e6d5
feat: add presence channel support across multiple languages and upda…
ArnabChatterjee20k May 14, 2026
1ee86d5
fix: remove dollar sign from additionalProperties key in model templates
ArnabChatterjee20k May 14, 2026
13d4275
updated
ArnabChatterjee20k May 14, 2026
ed91b65
fix: remove dollar sign from additionalProperties key in model templates
ArnabChatterjee20k May 14, 2026
48ea9b2
updated
ArnabChatterjee20k May 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 52 additions & 25 deletions mock-server/app/http.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@
require_once __DIR__ . '/../vendor/autoload.php';
}

use Swoole\Constant;
use Utopia\App;
use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\MockServer\Utopia\Exception;
use Utopia\MockServer\Utopia\File;
Expand All @@ -24,10 +22,11 @@
use Utopia\Validator\Host;
use Utopia\Validator\Nullable;
use Utopia\Validator\WhiteList;
use Swoole\Process;
use Swoole\Http\Server;
use Utopia\MockServer\Utopia\Model\Player;
use Utopia\MockServer\Utopia\Validator\Player as PlayerValidator;
use Utopia\MockServer\Utopia\Realtime\Protocol as RealtimeProtocol;
use Utopia\WebSocket\Adapter\Swoole;
use Utopia\WebSocket\Server as WebSocketServer;

const APP_AUTH_TYPE_SESSION = 'Session';
const APP_AUTH_TYPE_JWT = 'JWT';
Expand All @@ -39,23 +38,24 @@
const APP_PLATFORM_CONSOLE = 'console';
const APP_STORAGE_CACHE = '/storage/cache';

$http = new Server(
host: '0.0.0.0',
port: App::getEnv('PORT', 80),
mode: SWOOLE_PROCESS
);
$payloadSize = 6 * (1024 * 1024); // 6MB
$workerNumber = swoole_cpu_num() * intval(App::getEnv('_APP_WORKER_PER_CORE', 6));

$http->set([
'worker_num' => $workerNumber,
$adapter = new Swoole(host: '0.0.0.0', port: (int) App::getEnv('PORT', 80));
$adapter
->setPackageMaxLength($payloadSize)
->setWorkerNumber($workerNumber);

// Settings the adapter doesn't expose directly.
$adapter->getNative()->set([
'open_http2_protocol' => true,
'http_compression' => true,
'http_compression_level' => 6,
'package_max_length' => $payloadSize,
'buffer_output_size' => $payloadSize,
]);

$server = new WebSocketServer($adapter);

// Version Route for CLI
App::get('/v1/health/version')
->desc('Get version')
Expand Down Expand Up @@ -862,21 +862,23 @@ function ($utopia, $error, $request, $response) {
['utopia', 'error', 'request', 'response']
);

$http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize) {
/**
* Realtime WebSocket mock at /v1/realtime?project=<id>.
*
* Single Protocol instance is shared across worker invocations within the same
* worker process; per-connection state lives inside it keyed by Swoole fd.
*/
$realtimeProtocol = new RealtimeProtocol();

$server->error(function (\Throwable $error, string $action) {
Console::error("[ws:{$action}] " . $error->getMessage());
});

$server->onStart(function () use ($payloadSize) {
Console::success('Server started successfully (max payload is ' . number_format($payloadSize) . ' bytes)');
Console::info("Master pid {$http->master_pid}, manager pid {$http->manager_pid}");

// Listen for ctrl + c
Process::signal(
2,
function () use ($http) {
Console::log('Stop by Ctrl+C');
$http->shutdown();
}
);
});

$http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) {
$server->onRequest(function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) {
$request = new Request($swooleRequest);
$response = new UtopiaSwooleResponse($swooleResponse);

Expand All @@ -885,4 +887,29 @@ function () use ($http) {
$app->run($request, $response);
});

$http->start();
$server->onOpen(function (int $fd, SwooleRequest $swooleRequest) use ($server, $realtimeProtocol) {
$path = (string) ($swooleRequest->server['request_uri'] ?? '');
if ($path !== '/v1/realtime') {
// Reject upgrades on any other path with a policy-violation close.
$server->close($fd, 1008);
return;
}

$project = (string) ($swooleRequest->get['project'] ?? '');
if ($project === '') {
$server->close($fd, 1008);
return;
}

$realtimeProtocol->open($server, $fd, $project);
});

$server->onMessage(function (int $fd, string $data) use ($server, $realtimeProtocol) {
$realtimeProtocol->message($server, $fd, $data);
});

$server->onClose(function (int $fd) use ($realtimeProtocol) {
$realtimeProtocol->close($fd);
});

$server->start();
4 changes: 3 additions & 1 deletion mock-server/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
"utopia-php/framework": "0.33.*",
"utopia-php/database": "0.48.*",
"utopia-php/cli": "0.16.*",
"utopia-php/swoole": "0.8.*"
"utopia-php/swoole": "0.8.*",
"utopia-php/websocket": "^1.0",
"utopia-php/query": "^0.3.1"
},
"require-dev": {
"swoole/ide-helper": "5.1.2"
Expand Down
102 changes: 100 additions & 2 deletions mock-server/composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 59 additions & 0 deletions mock-server/src/Utopia/Realtime/Connection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

namespace Utopia\MockServer\Utopia\Realtime;

/**
* Per-connection state for a single WebSocket client.
*
* One instance lives per Swoole `fd` for as long as the socket is open.
*/
class Connection
{
public int $fd;
public string $project = '';
public ?array $user = null;

/**
* Active subscriptions keyed by client-supplied subscriptionId.
* Each value is `['channels' => string[], 'queries' => string[]]`.
*
* @var array<string, array{channels: string[], queries: string[]}>
*/
public array $subscriptions = [];

public function __construct(int $fd, string $project = '')
{
$this->fd = $fd;
$this->project = $project;
}

public function subscribe(string $subscriptionId, array $channels, array $queries): void
{
$this->subscriptions[$subscriptionId] = [
'channels' => array_values($channels),
'queries' => array_values($queries),
];
}

public function unsubscribe(string $subscriptionId): void
{
unset($this->subscriptions[$subscriptionId]);
}

/**
* Return subscription IDs whose channel set intersects the given channels.
*
* @param string[] $channels
* @return string[]
*/
public function matchingSubscriptions(array $channels): array
{
$matches = [];
foreach ($this->subscriptions as $id => $sub) {
if (!empty(array_intersect($channels, $sub['channels']))) {
$matches[] = $id;
}
}
return $matches;
}
}
Loading
Loading