mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 04:32:32 +00:00
Push: a system message's tag is its id; the receiver checks for a worker update on every open
Every session-watch push carried the tag "session_watch" (SystemMessage set Tag = type), so a browser that honours tags — Edge on the Windows device, iOS for notifications the worker shows itself — replaced an earlier completion with the next one; two watched sessions finishing minutes apart (18:38 and 18:40 UTC today) would have shown as one. The tag is now the payload id: a test, a pairing or a session watch never replaces another notification. Alerts keep their per-session tag, and the history is keyed by id and keeps every push either way. The iPhone kept running the worker without the navigate fix through two opens: a home-screen app that is only resumed never navigates, and the browser checks for a new worker on navigation. reconcileDevice now calls registration.update() on every open (fire-and-forget). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
+1
-1
Submodule internal updated: 57a0138dd5...3f2687b553
@@ -188,4 +188,19 @@ public class PushAlertProjectorTests
|
||||
Assert.Null(PushAlertProjector.BuildTopic(Constants.PortalSessionUrl("sess-1"), null));
|
||||
Assert.Null(PushAlertProjector.BuildTopic(Constants.PortalBaseUrl, Encoding.UTF8.GetBytes("key")));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void System_messages_never_share_a_tag_so_none_replaces_an_earlier_notification()
|
||||
{
|
||||
var scope = NotificationScope.Tenant("11111111-1111-1111-1111-111111111111");
|
||||
var now = new DateTime(2026, 10, 8, 18, 38, 40, DateTimeKind.Utc);
|
||||
var first = PushAlertProjector.SystemMessage("session_watch", "Enrollment finished", "A finished.", "success", scope, now, Constants.PortalSessionUrl("sess-1"));
|
||||
var second = PushAlertProjector.SystemMessage("session_watch", "Enrollment finished", "B finished.", "success", scope, now, Constants.PortalSessionUrl("sess-2"));
|
||||
|
||||
Assert.Equal(first.Id, first.Tag);
|
||||
Assert.Equal(second.Id, second.Tag);
|
||||
Assert.NotEqual(first.Tag, second.Tag);
|
||||
using var json = JsonDocument.Parse(PushAlertProjector.ToJsonBytes(first));
|
||||
Assert.Equal(first.Id, json.RootElement.GetProperty("notification").GetProperty("tag").GetString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,20 +133,29 @@ public static class PushAlertProjector
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>A system message to one device (test, paired, revoked, muted, paused, session watch).</summary>
|
||||
/// <summary>
|
||||
/// A system message to one device (test, paired, revoked, muted, paused, session watch). Its
|
||||
/// tag is its id: a system message never replaces an earlier notification where the browser
|
||||
/// honours tags, so two watched sessions finishing minutes apart stay two notifications
|
||||
/// (alerts keep the per-session tag, so a later verdict for the same session replaces the
|
||||
/// earlier one on the lock screen; the history keeps every push either way, keyed by id).
|
||||
/// </summary>
|
||||
public static PushPayload SystemMessage(string type, string title, string body, string severity, NotificationScope scope, DateTime nowUtc, string? portalUrl = null)
|
||||
=> new()
|
||||
{
|
||||
var id = Guid.NewGuid().ToString("N");
|
||||
return new()
|
||||
{
|
||||
Id = Guid.NewGuid().ToString("N"),
|
||||
Id = id,
|
||||
Type = type,
|
||||
Title = Clean(title, MaxTitleLength),
|
||||
Body = Clean(body, MaxBodyLength),
|
||||
Severity = severity,
|
||||
PortalUrl = portalUrl,
|
||||
Tag = type,
|
||||
Tag = id,
|
||||
Scope = scope.IsPlatform ? "platform" : "tenant",
|
||||
TimestampUtc = nowUtc,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Declarative Web Push JSON (<c>web_push: 8030</c>): Safari ≥ 18.4 shows it without the
|
||||
|
||||
@@ -70,6 +70,16 @@ export async function getPushRegistration(): Promise<ServiceWorkerRegistration |
|
||||
return registration ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asks the browser to re-fetch the worker script. A home-screen app that is only resumed never
|
||||
* navigates, and the browser checks for a new worker on navigation — the iPhone kept running the
|
||||
* worker without the navigate fix through two opens (2026-10-08). Fire-and-forget: the new worker
|
||||
* installs, skips waiting and claims the page on its own.
|
||||
*/
|
||||
export function checkForWorkerUpdate(registration: ServiceWorkerRegistration): void {
|
||||
registration.update().catch(() => {});
|
||||
}
|
||||
|
||||
export interface SubscriptionKeys {
|
||||
endpoint: string;
|
||||
p256dh: string;
|
||||
@@ -293,6 +303,7 @@ export async function reconcileDevice(): Promise<ReconcileResult> {
|
||||
try {
|
||||
const registration = (await getPushRegistration()) ?? (isPushSupported() ? await registerPushWorker() : null);
|
||||
if (registration) {
|
||||
checkForWorkerUpdate(registration);
|
||||
let subscription = await registration.pushManager.getSubscription();
|
||||
const permission = notificationPermission();
|
||||
const vapidPublicKey = meta[META_KEYS.vapidPublicKey];
|
||||
|
||||
Reference in New Issue
Block a user