Add push noty script and docs (#10380)
This commit is contained in:
@@ -0,0 +1,97 @@
|
|||||||
|
# push-notification
|
||||||
|
|
||||||
|
Sends sample APNS payloads to a booted iOS simulator via `xcrun simctl push`.
|
||||||
|
Useful for exercising `useNotificationsHandler` (`src/lib/hooks/useNotificationHandler.ts`)
|
||||||
|
without a real APNS round-trip — foreground display behavior, tap responses,
|
||||||
|
and notification-driven navigation.
|
||||||
|
|
||||||
|
## What this does and doesn't cover
|
||||||
|
|
||||||
|
**Covers** — anything in `useNotificationsHandler`:
|
||||||
|
|
||||||
|
- `setNotificationHandler` foreground behavior (banner, list, badge, sound flags)
|
||||||
|
- `addNotificationResponseReceivedListener` tap handling
|
||||||
|
- Account-switch flow when `recipientDid` differs from the signed-in account
|
||||||
|
- Navigation routing for each `reason` (post threads, profiles, conversations)
|
||||||
|
|
||||||
|
**Does not cover** — `BlueskyNSE` (the iOS Notification Service Extension):
|
||||||
|
|
||||||
|
- Communication Notification styling for chat messages
|
||||||
|
- Badge increment via `mutateWithBadge`
|
||||||
|
- Custom `dm.aiff` sound for chat messages
|
||||||
|
|
||||||
|
The simulator does not reliably invoke NSEs for `simctl push` on recent iOS
|
||||||
|
versions (verified bypassed on iOS 26.4). To test NSE behavior, run on a real
|
||||||
|
device with a real APNS push, or unit-test `NotificationService.didReceive`
|
||||||
|
directly in Xcode.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
1. Boot an iOS simulator and install the app:
|
||||||
|
```
|
||||||
|
yarn ios
|
||||||
|
```
|
||||||
|
2. Sign in to the account you'll be testing against. The `recipientDid` in
|
||||||
|
each payload is substituted at send time and must match the signed-in DID,
|
||||||
|
otherwise:
|
||||||
|
- Chat notifications trigger the account-switch flow
|
||||||
|
- Other reasons are silently dropped by the handler
|
||||||
|
3. Find your DID. Easiest: visit your profile in a web browser and copy it
|
||||||
|
from the URL, or grep dev logs for `currentAccount`.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
./send.sh <payload-name> [--did <did>] [--device <udid>] [--bundle <id>]
|
||||||
|
```
|
||||||
|
|
||||||
|
Pass the DID once via env var to avoid repeating it:
|
||||||
|
|
||||||
|
```
|
||||||
|
export BLUESKY_TEST_DID=did:plc:yourdidhere
|
||||||
|
|
||||||
|
./send.sh like
|
||||||
|
./send.sh chat-message
|
||||||
|
./send.sh follow
|
||||||
|
```
|
||||||
|
|
||||||
|
Defaults: `--device booted`, `--bundle xyz.blueskyweb.app`. Run `./send.sh --help`
|
||||||
|
for the full list of available payloads.
|
||||||
|
|
||||||
|
## Foreground vs background
|
||||||
|
|
||||||
|
`useNotificationsHandler` behaves differently depending on app state:
|
||||||
|
|
||||||
|
- **Foreground** — `setNotificationHandler.handleNotification` decides whether
|
||||||
|
to show a banner, play a sound, etc. For chat reasons, the banner is
|
||||||
|
suppressed if `payload.convoId === currentConvoId` (you're already viewing
|
||||||
|
that conversation).
|
||||||
|
- **Background or tapped** — `addNotificationResponseReceivedListener` fires
|
||||||
|
on tap and runs the navigation routing in `notificationToURL`.
|
||||||
|
|
||||||
|
To test the response listener, background the app first (`cmd+shift+H` in the
|
||||||
|
sim), send the push, then tap the banner.
|
||||||
|
|
||||||
|
## Available payloads
|
||||||
|
|
||||||
|
| Payload | Reason | Navigation target |
|
||||||
|
|---|---|---|
|
||||||
|
| `like.apns` | `like` | post thread (from `subject`) |
|
||||||
|
| `reply.apns` | `reply` | post thread (from `uri`) |
|
||||||
|
| `follow.apns` | `follow` | sender's profile (from `uri.host`) |
|
||||||
|
| `chat-message.apns` | `chat-message` | `MessagesConversation` with `convoId` |
|
||||||
|
| `chat-reaction.apns` | `chat-reaction` | `MessagesConversation` with `convoId` |
|
||||||
|
|
||||||
|
The `subject` AT URIs reference fake post rkeys, so the destination screens
|
||||||
|
will fail to load real content — that's expected. Routing exercises the
|
||||||
|
navigation path, not the data fetch.
|
||||||
|
|
||||||
|
## Adding a new payload
|
||||||
|
|
||||||
|
1. Copy an existing `.apns` file in `payloads/` whose shape matches.
|
||||||
|
2. Set `aps.mutable-content: 1` and a real `aps.alert.{title,body}`.
|
||||||
|
3. Match the payload shape to `NotificationPayload` in
|
||||||
|
`src/lib/hooks/useNotificationHandler.ts` for the `reason` you're testing.
|
||||||
|
4. Use `__RECIPIENT_DID__` as the placeholder for the recipient — `send.sh`
|
||||||
|
substitutes it at send time. Use it anywhere a DID needs to belong to the
|
||||||
|
logged-in user (typically `recipientDid`, and post `subject` for likes/replies).
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"aps": {
|
||||||
|
"alert": {
|
||||||
|
"title": "Ian",
|
||||||
|
"body": "Beep beep mfer"
|
||||||
|
},
|
||||||
|
"mutable-content": 1
|
||||||
|
},
|
||||||
|
"reason": "chat-message",
|
||||||
|
"convoId": "3mkb6y3tjzu2r",
|
||||||
|
"messageId": "3mkdjhe6sd227",
|
||||||
|
"recipientDid": "did:plc:3jpt2mvvsumj2r7eqk4gzzjz",
|
||||||
|
"senderDisplayName": "Ian",
|
||||||
|
"senderHandle": "iwsmith.bsky.social"
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"aps": {
|
||||||
|
"alert": {
|
||||||
|
"title": "Alice Test",
|
||||||
|
"body": "reacted to your message"
|
||||||
|
},
|
||||||
|
"mutable-content": 1
|
||||||
|
},
|
||||||
|
"reason": "chat-reaction",
|
||||||
|
"convoId": "3kfakeconvo0001",
|
||||||
|
"messageId": "3kfakemsg0001",
|
||||||
|
"recipientDid": "__RECIPIENT_DID__",
|
||||||
|
"senderDisplayName": "Alice Test",
|
||||||
|
"senderHandle": "alice.test"
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"aps": {
|
||||||
|
"alert": {
|
||||||
|
"title": "alice.test",
|
||||||
|
"body": "followed you"
|
||||||
|
},
|
||||||
|
"mutable-content": 1,
|
||||||
|
"sound": "default"
|
||||||
|
},
|
||||||
|
"reason": "follow",
|
||||||
|
"uri": "at://did:plc:senderdummy00000000000000/app.bsky.graph.follow/3kfakefollow001",
|
||||||
|
"subject": "at://__RECIPIENT_DID__",
|
||||||
|
"recipientDid": "__RECIPIENT_DID__"
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"aps": {
|
||||||
|
"alert": {
|
||||||
|
"title": "alice.test",
|
||||||
|
"body": "liked your post"
|
||||||
|
},
|
||||||
|
"mutable-content": 1,
|
||||||
|
"sound": "default"
|
||||||
|
},
|
||||||
|
"reason": "like",
|
||||||
|
"uri": "at://did:plc:senderdummy00000000000000/app.bsky.feed.like/3kfakelike0001",
|
||||||
|
"subject": "at://__RECIPIENT_DID__/app.bsky.feed.post/3kfakepost0001",
|
||||||
|
"recipientDid": "__RECIPIENT_DID__"
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"aps": {
|
||||||
|
"alert": {
|
||||||
|
"title": "alice.test",
|
||||||
|
"body": "replied to your post"
|
||||||
|
},
|
||||||
|
"mutable-content": 1,
|
||||||
|
"sound": "default"
|
||||||
|
},
|
||||||
|
"reason": "reply",
|
||||||
|
"uri": "at://did:plc:senderdummy00000000000000/app.bsky.feed.post/3kfakereply0001",
|
||||||
|
"subject": "at://__RECIPIENT_DID__/app.bsky.feed.post/3kfakepost0001",
|
||||||
|
"recipientDid": "__RECIPIENT_DID__"
|
||||||
|
}
|
||||||
Executable
+78
@@ -0,0 +1,78 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Sends a sample APNS payload to a booted iOS simulator. Useful for testing
|
||||||
|
# BlueskyNSE and useNotificationsHandler without a real APNS round-trip.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# scripts/push-test/send.sh <payload-name> [--did <did>] [--device <udid>] [--bundle <id>]
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# scripts/push-test/send.sh like --did did:plc:abc123
|
||||||
|
# BLUESKY_TEST_DID=did:plc:abc123 scripts/push-test/send.sh chat-message
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PAYLOAD_DIR="$SCRIPT_DIR/payloads"
|
||||||
|
|
||||||
|
device="booted"
|
||||||
|
bundle="xyz.blueskyweb.app"
|
||||||
|
did="${BLUESKY_TEST_DID:-}"
|
||||||
|
name=""
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--did) did="$2"; shift 2;;
|
||||||
|
--device) device="$2"; shift 2;;
|
||||||
|
--bundle) bundle="$2"; shift 2;;
|
||||||
|
-h|--help)
|
||||||
|
cat <<EOF
|
||||||
|
Usage: $0 <payload-name> [--did <did>] [--device <udid>] [--bundle <id>]
|
||||||
|
|
||||||
|
Available payloads:
|
||||||
|
$(ls "$PAYLOAD_DIR" 2>/dev/null | sed 's/\.apns$//' | sed 's/^/ /')
|
||||||
|
|
||||||
|
Pass --did or set BLUESKY_TEST_DID to substitute the recipient DID. The DID
|
||||||
|
must match the account currently signed in to the app, otherwise chat
|
||||||
|
notifications will trigger an account-switch flow and other reasons will be
|
||||||
|
silently dropped by the handler.
|
||||||
|
EOF
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [[ -z "$name" ]]; then
|
||||||
|
name="$1"
|
||||||
|
else
|
||||||
|
echo "Unknown arg: $1" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "$name" ]]; then
|
||||||
|
echo "Error: payload name required. Run with --help for options." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
src="$PAYLOAD_DIR/${name}.apns"
|
||||||
|
if [[ ! -f "$src" ]]; then
|
||||||
|
echo "Error: payload not found: $src" >&2
|
||||||
|
echo "Available:" >&2
|
||||||
|
ls "$PAYLOAD_DIR" | sed 's/\.apns$//' | sed 's/^/ /' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$did" ]]; then
|
||||||
|
echo "Error: missing recipient DID. Pass --did or set BLUESKY_TEST_DID." >&2
|
||||||
|
echo " The DID must match the account currently signed in to the app." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmp="$(mktemp -t bluesky-push.XXXXXX).apns"
|
||||||
|
trap 'rm -f "$tmp"' EXIT
|
||||||
|
sed "s|__RECIPIENT_DID__|$did|g" "$src" > "$tmp"
|
||||||
|
|
||||||
|
echo "Pushing '$name' to $device ($bundle)"
|
||||||
|
xcrun simctl push "$device" "$bundle" "$tmp"
|
||||||
|
echo "Delivered."
|
||||||
Reference in New Issue
Block a user