diff --git a/src/utils/proxy/validate-widget-data.js b/src/utils/proxy/validate-widget-data.js index b38ef4f79..24c8e8287 100644 --- a/src/utils/proxy/validate-widget-data.js +++ b/src/utils/proxy/validate-widget-data.js @@ -8,6 +8,13 @@ export default function validateWidgetData(widget, endpoint, data) { let dataParsed = data; let error; let mapping; + const mappings = widgets[widget.type]?.mappings; + if (mappings) { + mapping = Object.values(mappings).find((m) => m.endpoint === endpoint); + } + + if (mapping?.allowEmpty && Buffer.isBuffer(data) && data.length === 0) return true; + if (Buffer.isBuffer(data)) { try { dataParsed = JSON.parse(data); @@ -23,15 +30,11 @@ export default function validateWidgetData(widget, endpoint, data) { } if (dataParsed && Object.entries(dataParsed).length) { - const mappings = widgets[widget.type]?.mappings; - if (mappings) { - mapping = Object.values(mappings).find((m) => m.endpoint === endpoint); - mapping?.validate?.forEach((key) => { - if (dataParsed[key] === undefined) { - valid = false; - } - }); - } + mapping?.validate?.forEach((key) => { + if (dataParsed[key] === undefined) { + valid = false; + } + }); } if (!valid) { diff --git a/src/utils/proxy/validate-widget-data.test.js b/src/utils/proxy/validate-widget-data.test.js index 307b5fd5b..c2fb139e1 100644 --- a/src/utils/proxy/validate-widget-data.test.js +++ b/src/utils/proxy/validate-widget-data.test.js @@ -1,4 +1,4 @@ -import { describe, expect, it, vi } from "vitest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; const { loggerError } = vi.hoisted(() => ({ loggerError: vi.fn(), @@ -18,6 +18,10 @@ vi.mock("widgets/widgets", () => ({ endpoint: "foo", validate: ["a", "b"], }, + empty: { + endpoint: "empty", + allowEmpty: true, + }, }, }, }, @@ -26,6 +30,10 @@ vi.mock("widgets/widgets", () => ({ import validateWidgetData from "./validate-widget-data"; describe("utils/proxy/validate-widget-data", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + it("returns false when buffer JSON cannot be parsed", () => { expect(validateWidgetData({ type: "test" }, "foo", Buffer.from("not json"))).toBe(false); expect(loggerError).toHaveBeenCalled(); @@ -41,4 +49,9 @@ describe("utils/proxy/validate-widget-data", () => { expect(validateWidgetData({ type: "test" }, "foo", Buffer.from(JSON.stringify({ a: 1 })))).toBe(false); expect(loggerError).toHaveBeenCalled(); }); + + it("allows empty buffer responses for mappings that explicitly allow them", () => { + expect(validateWidgetData({ type: "test" }, "empty", Buffer.from(""))).toBe(true); + expect(loggerError).not.toHaveBeenCalled(); + }); }); diff --git a/src/widgets/ntfy/widget.js b/src/widgets/ntfy/widget.js index 577e124a7..a11f379d6 100644 --- a/src/widgets/ntfy/widget.js +++ b/src/widgets/ntfy/widget.js @@ -1,5 +1,14 @@ +import { asJson } from "utils/proxy/api-helpers"; import credentialedProxyHandler from "utils/proxy/handlers/credentialed"; +const noMessages = { + title: null, + message: null, + priority: 3, + time: null, + tags: [], +}; + const widget = { api: "{url}/{endpoint}", proxyHandler: credentialedProxyHandler, @@ -7,6 +16,14 @@ const widget = { mappings: { messages: { endpoint: "{topic}/json?poll=1&since=latest", + allowEmpty: true, + map: (data) => { + if (Buffer.isBuffer(data) && data.length === 0) { + return noMessages; + } + + return asJson(data); + }, }, }, }; diff --git a/src/widgets/ntfy/widget.test.js b/src/widgets/ntfy/widget.test.js index 4827c60b2..4025ce741 100644 --- a/src/widgets/ntfy/widget.test.js +++ b/src/widgets/ntfy/widget.test.js @@ -1,4 +1,4 @@ -import { describe, it } from "vitest"; +import { describe, expect, it } from "vitest"; import { expectWidgetConfigShape } from "test-utils/widget-config"; @@ -8,4 +8,18 @@ describe("ntfy widget config", () => { it("exports a valid widget config", () => { expectWidgetConfigShape(widget); }); + + it("maps an empty latest message response to the no messages state", () => { + expect(widget.mappings.messages.map(Buffer.from(""))).toEqual({ + title: null, + message: null, + priority: 3, + time: null, + tags: [], + }); + }); + + it("parses latest message responses", () => { + expect(widget.mappings.messages.map(Buffer.from('{"message":"hello"}'))).toEqual({ message: "hello" }); + }); });