The iOS app's icon count, pushed by ipx (#433)
Background refresh runs when iOS chooses, and for this app that has been rarely, so the icon's unread count lagged until the app was opened (#421). Now ipx sends the count: - ipx: a push_devices table and POST/DELETE /api/push/devices. With [push] set to an APNs key, a task compares each phone's All Subscriptions unread count with the last sent, once a minute, and sends a changed one to Apple as a badge-only push: no alert, no sound. A token Apple says is gone is forgotten. The signing token is ES256 over the team and time, made again every forty minutes, inside Apple's limits. - iOS: the aps-environment entitlement. The app registers for push once badge permission is given, each launch, and hands its token to the page. - Page: registers the token with ipx as the person signed in, since the player's address (ipxapi) lets through only the player's paths, and removes it on signing out. jsonwebtoken here is built without PEM support, so the .p8 key's base64 is decoded in push.rs rather than turning on a feature and changing Cargo.lock for one file. cargo test -p ipx on macOS: 182 pass; the 3 that fail read /proc and fail there without this change too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Added
|
||||
|
||||
- In the iOS app, the page registers the phone with ipx for its icon's count, and signing out removes it (#433).
|
||||
- In the iOS app, the icon's unread count follows the page's: reading and new items change it at once, not the next time the app is opened (#421).
|
||||
- In the iOS app, presses are felt: play, pause, flag, pull to refresh, marking read and errors each give the phone's haptic for it (#430).
|
||||
- A sleep timer in the player: 5 minutes to an hour, then playback fades out and pauses. In the iOS app the phone keeps the time, so it works with the screen locked; the app and Safari also get an AirPlay button. On a phone the title has the player's top line to itself, with the controls either side of play below it (#429).
|
||||
|
||||
@@ -847,7 +847,7 @@ $<HTMLButtonElement>('#prefs').onclick=prefsModal;
|
||||
// Someone the proxy signed in is signed out by the proxy: ipx's own sign-out cannot stick while
|
||||
// the proxy still vouches for them. /api/me says where, when that is the case.
|
||||
// What was kept for offline goes too: the next person on this device is not to read it (#389).
|
||||
$<HTMLButtonElement>('#signout').onclick=async()=>{ await api('/api/logout',{method:'POST'}); await clearOffline(); location.href=S.me?.sign_out||'/login'; };
|
||||
$<HTMLButtonElement>('#signout').onclick=async()=>{ await forgetPush(); await api('/api/logout',{method:'POST'}); await clearOffline(); location.href=S.me?.sign_out||'/login'; };
|
||||
api('/api/me').then(u=>{
|
||||
S.me=u;
|
||||
offlineOwner(u.name);
|
||||
|
||||
@@ -34,6 +34,18 @@ function hostBadge(n: number){
|
||||
try{ if(n!==badgeSent){ badgeSent=n; ipxHost?.({t:'unread', n}); } }catch{}
|
||||
}
|
||||
|
||||
/// This phone's push token (#433), which the app hands over at ready: registered with ipx here,
|
||||
/// signed in as the page is, so ipx can keep the icon's count right while the app is closed.
|
||||
let pushToken='';
|
||||
function hostPush(m: {token: string, sandbox?: boolean}){
|
||||
pushToken=m.token;
|
||||
api('/api/push/devices',{method:'POST',body:JSON.stringify({token:m.token,sandbox:!!m.sandbox})}).catch(()=>{});
|
||||
}
|
||||
/// Signing out: the phone's icon is no longer this person's to count.
|
||||
async function forgetPush(){
|
||||
if(pushToken) await api(`/api/push/devices/${encodeURIComponent(pushToken)}`,{method:'DELETE'}).catch(()=>{});
|
||||
}
|
||||
|
||||
/// A press felt as well as seen, in the app (#430): the phone's haptics, which a page cannot reach.
|
||||
/// `tap` for play, pause and the like, `done` for something finished (marked read), `warn` for an
|
||||
/// error. An app without them ignores the message; a browser has no host to send it to.
|
||||
@@ -166,6 +178,7 @@ function installNativePlayback(){
|
||||
// Sent at ready, so a page newer than the app offers only what the app can do.
|
||||
if(m.t==='host'){ hostCan.clear(); for(const c of m.can||[]) hostCan.add(c); paintRoute(); return; }
|
||||
if(m.t==='sleep'||m.t==='slept'){ hostSleep(m); return; }
|
||||
if(m.t==='push'){ hostPush(m); return; }
|
||||
if(!N.on) return;
|
||||
switch(m.t){
|
||||
case 'time':
|
||||
|
||||
Reference in New Issue
Block a user