Fix appwrite push poc fixes - #150
Conversation
Adds a self-hosted, low-power alternative to FCM/APNS. Publishes notifications over MQTT 5 to a per-device topic, allowing a single persistent TLS connection on the device with a long keep-alive interval (30 minutes by default) — the same model that lets FCM be low-power on Android. - Helpers/MQTT: minimal MQTT 5 codec (control packet encode/decode) so the adapter does not need an external MQTT client dependency. - Adapter/Push/Appwrite: publisher adapter. Connects over TCP/TLS, authenticates with a short-lived HMAC-signed JWT, publishes one QoS 1 PUBLISH per device with content-type and message-expiry properties, maps broker reason codes back to the standard expired-token signal so Appwrite's target invalidation works the same way as for FCM/APNS. Tests: 10 codec round-trip cases, 2 adapter integration cases driven by a fake broker spawned via proc_open. PHPStan level 6 clean, Pint PSR-12 clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Pipeline PUBLISHes up to the broker-advertised Receive Maximum (default 256) and match PUBACKs by packet id. Drops effective send time from N×RTT to ~RTT for large fan-outs. - Verify PUBACK packet id matches the in-flight publish so an out-of-order or duplicate ack cannot attribute success to the wrong device token (Greptile P2, Copilot). - Surface json_encode failures as a RuntimeException instead of silently sending an empty payload to the broker (Greptile P1). - Persistent read buffer on the adapter so coalesced TCP reads do not drop trailing MQTT packets between readPacket() calls (Copilot). - MQTT::encodeConnect throws when a password is supplied without a username (MQTT 5 §3.1.2.9, Greptile P2). - MQTT::encodePublish validates QoS is 0/1/2 instead of silently masking the bits (Copilot). - FakeBroker fixture rewritten on Swoole — drops the pcntl dependency that wasn't installed in the alpine test image, and exercises the same async runtime Appwrite uses in production. Dockerfile installs ext- swoole via PECL for the tests image. - New tests: pipelined send to 64 devices, password-without-username rejection, invalid-QoS rejection. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Swoole 6.x enables brotli compression by default and requires libbrotli-dev at build time + brotli-libs at runtime. Without them the configure step fails with "Package 'libbrotlienc' not found". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Thanks for contributing! This repository is a read-only mirror; development for this library happens in |
Greptile SummaryThe PR adds an MQTT 5 codec, an Appwrite MQTT push adapter with pipelined QoS 1 delivery, and fake-broker tests. The adapter currently misses inherited initialization, can omit recipient results after pipeline read failures, and its tests use an incompatible constructor API.
Confidence Score: 2/5The PR is not safe to merge until the adapter initialization, incomplete pipeline failure results, and broken test construction are fixed. Successful sends currently fail while recording metrics, mid-pipeline read failures can silently omit unsent recipients from the response, and the new adapter tests cannot instantiate the class. Files Needing Attention: src/Utopia/Messaging/Adapter/Push/Appwrite.php; tests/Messaging/Adapter/Push/AppwriteTest.php Important Files Changed
Prompt To Fix All With AI### Issue 1
src/Utopia/Messaging/Adapter/Push/Appwrite.php:54-66
**Inherited adapter state remains uninitialized**
Every successful `send()` records metrics after `process()` returns, but this constructor never calls `parent::__construct()`, so `recordSend()` accesses the uninitialized `sendCounter` property and the caller receives an `Error` instead of the delivery response.
### Issue 2
src/Utopia/Messaging/Adapter/Push/Appwrite.php:154-161
**Read failures omit queued recipients**
When a broker closes or times out while a batch exceeds `receiveMaximum`, this branch records failures only for the current in-flight window and returns normally. Recipients still waiting after the cursor are never sent and receive no result entry, preventing callers from identifying or retrying those notifications.
### Issue 3
tests/Messaging/Adapter/Push/AppwriteTest.php:18-22
**Tests use an unknown constructor argument**
Each new adapter test passes `signingKey`, but the constructor has no parameter by that name and instead requires `projectId` and `credential`. PHP therefore raises an unknown named-parameter `Error` during construction, so these tests never exercise the adapter.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (1): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile |
| public function __construct( | ||
| private string $endpoint, | ||
| private string $projectId, | ||
| private string $credential, | ||
| private string $authMethod = 'appwrite-jwt', | ||
| private bool $tls = true, | ||
| private int $messageExpiry = self::DEFAULT_MESSAGE_EXPIRY, | ||
| private string $clientId = '', | ||
| ) { | ||
| if ($this->clientId === '') { | ||
| $this->clientId = self::CLIENT_PREFIX . '-' . \bin2hex(\random_bytes(6)); | ||
| } | ||
| } |
There was a problem hiding this comment.
Inherited adapter state remains uninitialized
Every successful send() records metrics after process() returns, but this constructor never calls parent::__construct(), so recordSend() accesses the uninitialized sendCounter property and the caller receives an Error instead of the delivery response.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/Utopia/Messaging/Adapter/Push/Appwrite.php
Line: 54-66
Comment:
**Inherited adapter state remains uninitialized**
Every successful `send()` records metrics after `process()` returns, but this constructor never calls `parent::__construct()`, so `recordSend()` accesses the uninitialized `sendCounter` property and the caller receives an `Error` instead of the delivery response.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| try { | ||
| $ack = $this->readPacket($socket); | ||
| } catch (\Throwable $error) { | ||
| foreach ($inflight as $token) { | ||
| $response->addResult($token, $error->getMessage()); | ||
| } | ||
| return; | ||
| } |
There was a problem hiding this comment.
Read failures omit queued recipients
When a broker closes or times out while a batch exceeds receiveMaximum, this branch records failures only for the current in-flight window and returns normally. Recipients still waiting after the cursor are never sent and receive no result entry, preventing callers from identifying or retrying those notifications.
Knowledge Base Used:
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/Utopia/Messaging/Adapter/Push/Appwrite.php
Line: 154-161
Comment:
**Read failures omit queued recipients**
When a broker closes or times out while a batch exceeds `receiveMaximum`, this branch records failures only for the current in-flight window and returns normally. Recipients still waiting after the cursor are never sent and receive no result entry, preventing callers from identifying or retrying those notifications.
**Knowledge Base Used:**
- [Push notification delivery](https://app.greptile.com/appwrite/-/custom-context/knowledge-base/utopia-php/messaging/-/docs/push-notification-delivery.md)
- [Message model, priorities, and delivery results](https://app.greptile.com/appwrite/-/custom-context/knowledge-base/utopia-php/messaging/-/docs/message-model-and-results.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| $adapter = new Appwrite( | ||
| endpoint: '127.0.0.1:' . $broker['port'], | ||
| signingKey: self::SIGNING_KEY, | ||
| tls: false, | ||
| ); |
There was a problem hiding this comment.
Tests use an unknown constructor argument
Each new adapter test passes signingKey, but the constructor has no parameter by that name and instead requires projectId and credential. PHP therefore raises an unknown named-parameter Error during construction, so these tests never exercise the adapter.
Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/Messaging/Adapter/Push/AppwriteTest.php
Line: 18-22
Comment:
**Tests use an unknown constructor argument**
Each new adapter test passes `signingKey`, but the constructor has no parameter by that name and instead requires `projectId` and `credential`. PHP therefore raises an unknown named-parameter `Error` during construction, so these tests never exercise the adapter.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
What does this PR do?
(Provide a description of what this PR does.)
Test plan
(Write your test plan here. If you changed any code, please provide us with clear instructions on how you verified your changes work.)
Related PRs and issues
(If this PR is related to any other PR or resolves any issue or related to any issue link all related PR and issues here.)
Have you read the contributing guidelines on issues?
(Write your answer here.)