Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
53 changes: 47 additions & 6 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@ on:
workflow_dispatch:
inputs:
docs_version:
description: 'Docs version to publish, e.g. 1.1.0'
required: true
description: 'Docs version to publish, e.g. 1.1.0. Leave blank to publish develop.'
required: false
type: string
source_ref:
description: 'Git ref to build from. Defaults to docs_version prefixed with v when available.'
required: false
type: string
update_latest:
description: 'Also move latest alias/default redirect to this version'
required: true
description: 'Also move latest alias/default redirect to this version. Ignored when docs_version is blank.'
required: false
default: false
type: boolean

Expand Down Expand Up @@ -65,7 +65,7 @@ jobs:
if [[ "$GITHUB_EVENT_NAME" == "push" ]]; then
echo "checkout_ref=$GITHUB_SHA" >> "$GITHUB_OUTPUT"
echo "docs_version=develop" >> "$GITHUB_OUTPUT"
echo "update_latest=false" >> "$GITHUB_OUTPUT"
echo "update_latest=auto" >> "$GITHUB_OUTPUT"
exit 0
fi

Expand All @@ -91,6 +91,18 @@ jobs:
exit 0
fi

if [[ -z "$INPUT_DOCS_VERSION" ]]; then
source_ref="$INPUT_SOURCE_REF"
if [[ -z "$source_ref" ]]; then
source_ref="develop"
fi

echo "checkout_ref=$source_ref" >> "$GITHUB_OUTPUT"
echo "docs_version=develop" >> "$GITHUB_OUTPUT"
echo "update_latest=auto" >> "$GITHUB_OUTPUT"
exit 0
fi

docs_version="${INPUT_DOCS_VERSION#v}"
validate_docs_version "$docs_version"

Expand Down Expand Up @@ -141,7 +153,36 @@ jobs:
run: |
set -euo pipefail

if [[ "$UPDATE_LATEST" == "true" ]]; then
should_update_latest="$UPDATE_LATEST"

if [[ "$should_update_latest" == "auto" ]]; then
versions_file="$RUNNER_TEMP/docs-versions.json"

if git show origin/gh-pages:versions.json > "$versions_file" 2>/dev/null \
&& python3 - "$versions_file" <<'PY'
import json
import re
import sys

with open(sys.argv[1], encoding='utf-8') as handle:
versions = json.load(handle)

stable_version = re.compile(r'^[0-9]+\.[0-9]+\.[0-9]+$')

for item in versions:
if stable_version.fullmatch(str(item.get('version', ''))):
sys.exit(0)

sys.exit(1)
PY
then
should_update_latest=false
else
should_update_latest=true
fi
fi

if [[ "$should_update_latest" == "true" ]]; then
mike deploy --push --update-aliases "$DOCS_VERSION" latest
mike set-default --push latest
else
Expand Down
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.ht
Server-Sent Events through Redis Pub/Sub.
- Channel authorization and authenticated-user resolution contracts.
- Streaming response support for supported CodeIgniter releases.
- Framework-independent browser `SseClient` ES module.
- Framework-independent browser `SseClient` ES module with server-selected,
broker-neutral stream bootstrap.
- Redis subscriber health PINGs, bounded reconnects, payload/RESP safety
limits, and event-ID deduplication.
- Mercure 0.x Hub publisher, exact topic mapping, private subscriber JWT
Expand Down
19 changes: 17 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,14 @@ Accept: text/event-stream
Use the included framework-independent ES module:

```javascript
import { SseClient } from '/vendor/codeigniter4-sse/sse-client.js';
import {
RedisSseAdapter,
SseClient,
} from '/vendor/codeigniter4-sse/sse-client.js';

const live = new SseClient({
endpoint: '/sse',
adapter: new RedisSseAdapter(),
channels: [`users.${currentUserId}`],
withCredentials: true,
});
Expand All @@ -96,6 +100,9 @@ live.on('status', ({ status }) => {
live.connect();
```

`SseClient` opens EventSource through the selected frontend adapter. When the
server broker changes, update the adapter class in the browser client.

The browser's native `EventSource` automatically reconnects when a connection
ends. With Redis, the package intentionally limits the PHP stream lifetime.
With Mercure, the browser streams directly from the Hub.
Expand Down Expand Up @@ -193,13 +200,21 @@ authorization request that sets a topic-scoped HttpOnly JWT cookie, and the
browser client connects directly to the Hub:

```javascript
import {
MercureSseAdapter,
SseClient,
} from '/vendor/codeigniter4-sse/sse-client.js';

const live = new SseClient({
endpoint: '/sse',
transport: 'mercure',
adapter: new MercureSseAdapter(),
channels: [`users.${currentUserId}`],
});
```

Use the frontend adapter that matches the configured broker. Mercure's adapter
authorizes through the package route and then opens EventSource on the Hub.

Mercure can replay retained Hub history through `Last-Event-ID`. See
[Mercure Hub](docs/mercure.md) for Docker, signing keys, authorization,
cookies, CORS, and reverse-proxy configuration.
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
"Tests\\": "tests/",
"Support\\Tests\\": "tests/_support/"
}
},
"scripts": {
Expand Down
5 changes: 5 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ Application service / controller / worker
Both paths use the same event envelope, channel authorizer, browser event
handlers, and `sse()->publish(...)` API.

The browser uses a frontend adapter that matches the configured broker. Redis,
local, and in-memory adapters open EventSource directly on the CodeIgniter
route. Mercure first calls the same route for topic authorization and then
opens EventSource on the Hub.

## Public application boundary

Normal application code uses the high-level service:
Expand Down
Loading