Skip to content

Fix appwrite push poc fixes - #150

Closed
ArnabChatterjee20k wants to merge 5 commits into
mainfrom
fix-appwrite-push-poc-fixes
Closed

Fix appwrite push poc fixes#150
ArnabChatterjee20k wants to merge 5 commits into
mainfrom
fix-appwrite-push-poc-fixes

Conversation

@ArnabChatterjee20k

Copy link
Copy Markdown

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.)

abnegate and others added 5 commits May 23, 2026 15:29
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>
@github-actions

Copy link
Copy Markdown

Thanks for contributing! This repository is a read-only mirror; development for this library happens in packages/messaging in the utopia-php monorepo. Please open this pull request there instead.

@github-actions github-actions Bot closed this Aug 25, 2026
@greptile-apps

greptile-apps Bot commented Aug 25, 2026

Copy link
Copy Markdown

Greptile Summary

The 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.

  • Adds MQTT 5 packet encoding, decoding, and property parsing.
  • Adds authenticated Appwrite push delivery with broker receive-window handling.
  • Adds adapter, codec, and fake-broker test coverage.

Confidence Score: 2/5

The 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

Filename Overview
src/Utopia/Messaging/Adapter/Push/Appwrite.php Adds the Appwrite MQTT delivery path, but omits base-adapter initialization and drops outcomes for unsent recipients after read failures.
src/Utopia/Messaging/Helpers/MQTT.php Adds the required MQTT 5 codec; unknown properties truncate parsing, but no current actionable interoperability failure was established.
tests/Messaging/Adapter/Push/AppwriteTest.php Adds integration-style adapter tests, but every test passes a nonexistent signingKey named argument and cannot construct the adapter.
tests/Messaging/Adapter/Push/FakeBroker.php Adds a Swoole-based fake MQTT broker supporting the tested CONNECT, PUBLISH, PUBACK, and disconnect flow.
tests/Messaging/Helpers/MQTTTest.php Adds focused round-trip and buffering coverage for the MQTT codec without an independently actionable defect.

Fix all with Greploop Fix All in Claude Code Fix All in Codex

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

Comment on lines +54 to +66
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));
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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.

Fix in Claude Code Fix in Codex

Comment on lines +154 to +161
try {
$ack = $this->readPacket($socket);
} catch (\Throwable $error) {
foreach ($inflight as $token) {
$response->addResult($token, $error->getMessage());
}
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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.

Fix in Claude Code Fix in Codex

Comment on lines +18 to +22
$adapter = new Appwrite(
endpoint: '127.0.0.1:' . $broker['port'],
signingKey: self::SIGNING_KEY,
tls: false,
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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.

Fix in Claude Code Fix in Codex

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants