Gateway: status route with per-route results, test push, reconnect and unpair
This commit is contained in:
1 parent
e6fd2b8f51
commit
9707daf1fe
9 files changed
+398
-55
No files matched your search
@@ -0,0 +1,63 @@
|
||||
// Delivers pushes to paired watches through APNs, and remembers Apple's last answer so the watch's
|
||||
// status screen can show whether pushes are getting through. Without APNs settings it only logs.
|
||||
|
||||
import type { Apns } from "./apns.ts";
|
||||
import type { PushResult } from "./contract.ts";
|
||||
import type { Db, DeviceRow } from "./db.ts";
|
||||
import type { Push } from "./notify.ts";
|
||||
|
||||
export class Pusher {
|
||||
/** Apple's answer to the most recent push sent to any watch. */
|
||||
last: PushResult | null = null;
|
||||
|
||||
constructor(
|
||||
private readonly db: Db,
|
||||
private readonly apns: Apns | null,
|
||||
private readonly log: (message: string) => void = () => {},
|
||||
) {}
|
||||
|
||||
get configured() {
|
||||
return this.apns !== null;
|
||||
}
|
||||
|
||||
/** Sends to every paired watch that registered for pushes. */
|
||||
sendAll = async (push: Push): Promise<void> => {
|
||||
const devices = this.db.devices.active().filter((device) => device.apns_token && device.apns_env);
|
||||
if (!this.apns || devices.length === 0) {
|
||||
this.log(`push (not sent): [${push.category}] ${push.title}: ${push.body}`);
|
||||
return;
|
||||
}
|
||||
await Promise.all(devices.map((device) => this.sendTo(device, push)));
|
||||
};
|
||||
|
||||
/** Sends to one watch and returns what Apple said. */
|
||||
async sendTo(device: DeviceRow, push: Push): Promise<PushResult> {
|
||||
const at = new Date().toISOString();
|
||||
if (!this.apns) return { ok: false, at, status: 0, reason: "The gateway has no APNs key configured." };
|
||||
if (!device.apns_token || !device.apns_env) {
|
||||
return { ok: false, at, status: 0, reason: "This watch has not registered for notifications." };
|
||||
}
|
||||
const payload = {
|
||||
aps: {
|
||||
alert: { title: push.title, ...(push.subtitle ? { subtitle: push.subtitle } : {}), body: push.body },
|
||||
sound: "default",
|
||||
category: push.category,
|
||||
"thread-id": push.data.threadId ?? push.data.hostId ?? push.category,
|
||||
},
|
||||
...push.data,
|
||||
};
|
||||
const answer = await this.apns.send({
|
||||
deviceToken: device.apns_token,
|
||||
environment: device.apns_env,
|
||||
payload,
|
||||
collapseId: push.collapseId,
|
||||
});
|
||||
const result: PushResult = answer.ok ? { ok: true, at, status: 200, reason: null } : { ok: false, at, status: answer.status, reason: answer.reason };
|
||||
this.last = result;
|
||||
if (!answer.ok) {
|
||||
this.log(`APNs refused a push to ${device.name}: ${answer.status} ${answer.reason}`);
|
||||
if (answer.tokenInvalid) this.db.devices.clearPush(device.apns_token);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user