{"product":"edge-apps","releases":[{"date":"2026-08-01","entries":[{"areas":["developers"],"breaking":false,"content_html":"\u003cp\u003eA weather app on a lobby screen that loses its API for 10 minutes has 2 choices: show an error to\neveryone walking past, or keep showing the last forecast it had. \u003ccode\u003ewriteEdgeAppCache()\u003c/code\u003e and\n\u003ccode\u003ereadEdgeAppCache()\u003c/code\u003e make the second one a few lines of code:\u003c/p\u003e\n\u003cdiv class=\"code-block\"\u003e\n  \u003cbutton type=\"button\" class=\"code-copy\" aria-label=\"Copy code\" data-copy-code\u003e\n    \u003csvg data-copy viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.75\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"\u003e\n      \u003crect x=\"9\" y=\"9\" width=\"11\" height=\"11\" rx=\"2\"\u003e\u003c/rect\u003e\n      \u003cpath d=\"M5 15V5a2 2 0 0 1 2-2h10\"\u003e\u003c/path\u003e\n    \u003c/svg\u003e\n    \u003csvg data-check viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"\u003e\n      \u003cpath d=\"M20 6 9 17l-5-5\"\u003e\u003c/path\u003e\n    \u003c/svg\u003e\n  \u003c/button\u003e\n  \u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#d0d0d0;background-color:#202020;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-typescript\" data-lang=\"typescript\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#6ab825;font-weight:bold\"\u003eimport\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  getSettingWithDefault,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  readEdgeAppCache,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  writeEdgeAppCache,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e} \u003cspan style=\"color:#6ab825;font-weight:bold\"\u003efrom\u003c/span\u003e \u003cspan style=\"color:#ed9d13\"\u003e\u0026#39;@screenly/edge-apps\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#6ab825;font-weight:bold\"\u003easync\u003c/span\u003e \u003cspan style=\"color:#6ab825;font-weight:bold\"\u003efunction\u003c/span\u003e loadWeather() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#6ab825;font-weight:bold\"\u003etry\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#6ab825;font-weight:bold\"\u003econst\u003c/span\u003e response = \u003cspan style=\"color:#6ab825;font-weight:bold\"\u003eawait\u003c/span\u003e fetch(weatherApiUrl)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#6ab825;font-weight:bold\"\u003eif\u003c/span\u003e (!response.ok) \u003cspan style=\"color:#6ab825;font-weight:bold\"\u003ethrow\u003c/span\u003e \u003cspan style=\"color:#6ab825;font-weight:bold\"\u003enew\u003c/span\u003e \u003cspan style=\"color:#24909d\"\u003eError\u003c/span\u003e(\u003cspan style=\"color:#ed9d13\"\u003e`Weather API returned \u003c/span\u003e\u003cspan style=\"color:#ed9d13\"\u003e${\u003c/span\u003eresponse.status\u003cspan style=\"color:#ed9d13\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#ed9d13\"\u003e`\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#6ab825;font-weight:bold\"\u003econst\u003c/span\u003e weather = \u003cspan style=\"color:#6ab825;font-weight:bold\"\u003eawait\u003c/span\u003e response.json()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    writeEdgeAppCache(\u003cspan style=\"color:#ed9d13\"\u003e\u0026#39;weather-app\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#ed9d13\"\u003e\u0026#39;forecast\u0026#39;\u003c/span\u003e, weather)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#6ab825;font-weight:bold\"\u003ereturn\u003c/span\u003e weather\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  } \u003cspan style=\"color:#6ab825;font-weight:bold\"\u003ecatch\u003c/span\u003e (error) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#6ab825;font-weight:bold\"\u003eif\u003c/span\u003e (getSettingWithDefault(\u003cspan style=\"color:#ed9d13\"\u003e\u0026#39;display_errors\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#6ab825;font-weight:bold\"\u003efalse\u003c/span\u003e)) \u003cspan style=\"color:#6ab825;font-weight:bold\"\u003ethrow\u003c/span\u003e error\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#6ab825;font-weight:bold\"\u003ereturn\u003c/span\u003e readEdgeAppCache(\u003cspan style=\"color:#ed9d13\"\u003e\u0026#39;weather-app\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#ed9d13\"\u003e\u0026#39;forecast\u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n\u003cp\u003eValues are stored in the player\u0026rsquo;s \u003ccode\u003elocalStorage\u003c/code\u003e as JSON under \u003ccode\u003enamespace:key\u003c/code\u003e, so 2 apps, or 2\ncaches in one app, don\u0026rsquo;t overwrite each other. Anything JSON can hold works as a value.\u003c/p\u003e\n\u003cp\u003eThe cache is for fallback, not freshness. Nothing expires, so read it only after a real failure,\nand expect it to be empty after the player restarts. Both functions fail quietly:\n\u003ccode\u003ereadEdgeAppCache()\u003c/code\u003e returns \u003ccode\u003enull\u003c/code\u003e when there\u0026rsquo;s nothing usable, and \u003ccode\u003ewriteEdgeAppCache()\u003c/code\u003e does\nnothing when storage is full or unavailable, so neither needs its own \u003ccode\u003etry\u003c/code\u003e.\u003c/p\u003e\n","description":"readEdgeAppCache and writeEdgeAppCache store the last value an Edge App fetched successfully, so it has something to show when the next request fails.","title":"Edge Apps can keep showing the last good data when a fetch fails","type":"release","url":"https://www.screenly.io/releases/edge-apps/1.4.0/last-known-good-cache/"}],"json":"https://www.screenly.io/releases/edge-apps/1.4.0/v1.json","product":"edge-apps","product_label":"Edge Apps library","url":"https://www.screenly.io/releases/edge-apps/1.4.0/","version":"1.4.0"}],"title":"Edge Apps library 1.4.0","url":"https://www.screenly.io/releases/edge-apps/1.4.0/","version":1}