Push registers with registerPush, falling back on servers without it
MergedThe app now registers and withdraws its FCM token through social.grain.notification.registerPush / unregisterPush. It sends the old registerToken / unregisterToken only when the new method is missing: 404, 501, or an XRPC error named MethodNotImplemented or MethodNotFound. Every other failure is rethrown, so a 401, InvalidRequest or 500 never triggers a second call.
I read the diff and the types and helpers it relies on, and found nothing that must change.
XrpcException.Http(statusCode, body)is the shapeisMethodMissingexpects. A malformed or null body is handled byrunCatchingandorEmpty.- The
StubServerhelpers the new tests use all exist:on(..., code=),lastCall,countOf,calls,shutdown, andxrpc()as used elsewhere in the tests. - The tests cover the request shape, each fallback trigger, the no-fallback cases, and
isMethodMissingon its own.
I did not run the suite and have not looked at the test workflow result.
The pull already names two limits: it was not tried on a device, and debug builds send the release appId.
Carried forward: the change this pull makes at f901c0677485fcd4975cf10247b11c8fa385b3d9 is the one reviewed at e4fac497d2ea9185367a0850d3a1c05020d1ce13, so that review stands. What is new comes from its base.
Carried forward: the change this pull makes at ba54bcbcdcb13867f30037f3d7d89b1c696e58c8 is the one reviewed at f901c0677485fcd4975cf10247b11c8fa385b3d9, so that review stands. What is new comes from its base.

What changes
The app registers its FCM token with
social.grain.notification.registerPush, and withdraws it withsocial.grain.notification.unregisterPush. Both take the input of Bluesky'sapp.bsky.notification.registerPush:serviceDid(did:web:grain.social),token,platform(android) andappId(social.grain, the releaseapplicationId).When the server answers 404, 501, or an XRPC error named
MethodNotImplementedorMethodNotFound,registerPushToken/unregisterPushTokensend the olderregisterToken/unregisterTokencall exactly as today ({token, platform: "fcm"}and{token}). Any other failure, such as a 401 or a 400InvalidRequest, is thrown as before, andPushManagerlogs it; the older call is not tried.Auth is unchanged: the new call goes through the same client and auth context.
PushManager's triggers are unchanged (launch and sign-in, FCM token rotation, account switch, unregister on sign-out).Why
The Rust server serves only
registerPush/unregisterPush, shaped as Bluesky's. Production still runs the old server until cutover, and that has noregisterPush(it answers 404 today), so the app has to work against both until then.How it was checked
EndpointsTestcovers:registerPushandunregisterPushrequest: POST, the DPoP authorization, and a body of exactly the four fields, with no old call made;registerTokenand tounregisterTokenon each of 404, 501, 400MethodNotImplementedand 400MethodNotFound, with the old body exactly as before;InvalidRequestor 500, for both, with the error still thrown;isMethodMissingon its own.Left to the
testworkflow on the runner (JVM suite and the coverage gate); not built locally.Shortcomings
registerPushis untested.appIdis the releasesocial.grainin every build, including debug builds whose id carries.debug.🤖 Generated with Claude Code