mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-05 15:09:42 +02:00
feat(approvals): gate push Approve/Deny buttons on the opt-in setting too
One switch now governs the whole feature: with approvalsInboxEnabled off (the default), sendPushNotifications strips the actions and approvalId from permission push payloads, so the buttons no longer render at all (pre-inbox they rendered and did nothing). The page-side action relay is gated the same way for stale notifications sent before the toggle flipped. Only the store and answer endpoints keep running, so enabling the toggle surfaces anything already pending immediately. sendPushNotifications is async now (cached settings read); all call sites were already fire-and-forget. Covered by three new payload tests alongside the existing hostTitle suite. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -126,10 +126,12 @@ Object.assign(CodemanApp.prototype, {
|
||||
|
||||
/**
|
||||
* Push-notification action relay (sw.js → settings-ui notification-click →
|
||||
* here). Falls back to opening the session when the item is unknown.
|
||||
* here). Falls back to opening the session when the item is unknown, or
|
||||
* when the inbox is disabled (a stale notification from before the toggle
|
||||
* flipped can still carry an action).
|
||||
*/
|
||||
handleNotificationAction(action, approvalId, sessionId) {
|
||||
if ((action === 'approve' || action === 'deny') && approvalId) {
|
||||
if ((action === 'approve' || action === 'deny') && approvalId && this.approvalsInboxEnabled()) {
|
||||
this.answerApproval(approvalId, action);
|
||||
return;
|
||||
}
|
||||
|
||||
+6
-4
@@ -791,10 +791,12 @@ export const SettingsUpdateSchema = z
|
||||
*/
|
||||
agentSkillEnabled: z.boolean().optional(),
|
||||
/**
|
||||
* Approvals Inbox UI (header bell + drawer, phone overview answer buttons).
|
||||
* SYNCED, default OFF (opt-in): even with items pending, no surface renders
|
||||
* until this is enabled. The server-side store and answer endpoints run
|
||||
* regardless, so push Approve/Deny actions keep working either way.
|
||||
* Approvals Inbox (header bell + drawer, phone overview answer buttons,
|
||||
* push Approve/Deny action buttons). SYNCED, default OFF (opt-in): even
|
||||
* with items pending, no surface renders and push payloads carry no
|
||||
* actions/approvalId until this is enabled. The server-side store and the
|
||||
* answer endpoints run regardless, so flipping it ON shows anything
|
||||
* already pending immediately.
|
||||
*/
|
||||
approvalsInboxEnabled: z.boolean().optional(),
|
||||
tunnelEnabled: z.boolean().optional(),
|
||||
|
||||
+18
-3
@@ -2104,13 +2104,27 @@ export class WebServer extends EventEmitter {
|
||||
* Only events in PUSH_EVENT_MAP trigger push. Per-subscription preferences are checked.
|
||||
* Expired subscriptions (410/404) are auto-removed.
|
||||
*/
|
||||
private sendPushNotifications(event: string, data: Record<string, unknown>): void {
|
||||
// Async only for the Approvals Inbox settings read below; every call site is
|
||||
// fire-and-forget (the EventPort signature stays `void`).
|
||||
private async sendPushNotifications(event: string, data: Record<string, unknown>): Promise<void> {
|
||||
const template = WebServer.PUSH_EVENT_MAP[event];
|
||||
if (!template) return;
|
||||
|
||||
const subscriptions = this.pushStore.getAll();
|
||||
if (subscriptions.length === 0) return;
|
||||
|
||||
// Approvals Inbox gating: the Approve/Deny action buttons answer through
|
||||
// the inbox, so both the buttons and the approvalId they act on ship only
|
||||
// when the OPT-IN `approvalsInboxEnabled` setting is on (default OFF).
|
||||
// Pre-inbox these buttons rendered and did nothing; stripping them when
|
||||
// the feature is off is the honest shape. Cheap: the settings read is
|
||||
// cached (~2s TTL) and only taken for events that carry approval parts.
|
||||
let approvalsEnabled = false;
|
||||
if (template.actions || typeof data.approvalId === 'string') {
|
||||
const settings = await this.readSettings();
|
||||
approvalsEnabled = settings.approvalsInboxEnabled === true;
|
||||
}
|
||||
|
||||
const vapidKeys = this.pushStore.getVapidKeys();
|
||||
webpush.setVapidDetails('mailto:codeman@localhost', vapidKeys.publicKey, vapidKeys.privateKey);
|
||||
|
||||
@@ -2154,9 +2168,10 @@ export class WebServer extends EventEmitter {
|
||||
sessionId,
|
||||
// Approvals Inbox item id: lets sw.js answer an Approve/Deny action
|
||||
// click directly (POST /api/approvals/:id/answer) with no tab open.
|
||||
approvalId: typeof data.approvalId === 'string' ? data.approvalId : undefined,
|
||||
// Gated on the opt-in setting together with the action buttons.
|
||||
approvalId: approvalsEnabled && typeof data.approvalId === 'string' ? data.approvalId : undefined,
|
||||
urgency: template.urgency,
|
||||
actions: template.actions,
|
||||
actions: approvalsEnabled ? template.actions : undefined,
|
||||
});
|
||||
|
||||
for (const sub of subscriptions) {
|
||||
|
||||
Reference in New Issue
Block a user