Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions hledger-lib/locale/de.po
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,9 @@ msgstr "Zurück"
msgid "Saved journal {file}"
msgstr "Journal {file} gespeichert"

msgid "hledger-web is no longer running. It exits after fifteen minutes with no browser window showing it, or when stopped. To continue, start it again and reload this page."
msgstr "hledger-web läuft nicht mehr. Es beendet sich nach fünfzehn Minuten ohne ein Browserfenster, das es anzeigt, oder wenn es angehalten wird. Zum Weiterarbeiten starten Sie es neu und laden diese Seite erneut."

msgctxt "column heading"
msgid "Commodity"
msgstr "Einheit"
Expand Down
17 changes: 10 additions & 7 deletions hledger-web/Hledger/Web/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -159,12 +159,12 @@ web opts0 j = do
let warpsettings = setHost (fromString h) (setPort p defaultSettings)
if server_mode_ opts == ServeBrowse
then do
putStrLn "This server will exit after 2m with no browser windows open (or press ctrl-c)"
putStrLn "This server will exit after 15m with no browser windows open (or press ctrl-c)"
putStrLn "Opening web browser..."
hFlush stdout
-- returns normally only after the idle exit (ctrl-c or a server failure raises instead)
serveAndBrowse warpsettings (snd <$> mtcpsock) u app
putStrLn "No browser windows were open for 2m, exiting. (Use --serve to serve without this timeout.)"
putStrLn "No browser windows were open for 15m, exiting. (Use --serve to serve without this timeout.)"

else do
putStrLn "Press ctrl-c to quit"
Expand Down Expand Up @@ -195,8 +195,8 @@ web opts0 j = do
(Nothing, Nothing) -> Network.Wai.Handler.Warp.runSettings warpsettings app

-- | Browse mode: serve the app, open the default web browser on it once it
-- is listening, and return when no browser window has shown it for two
-- minutes. A page says it is open by pinging /_ping while it is (see
-- is listening, and return when no browser window has shown it for
-- browseIdleSeconds. A page says it is open by pinging /_ping while it is (see
-- browsePingInit in static/hledger.js). The pings are answered here, before
-- they reach the app, and the time of the latest one is kept.
-- With --port 0 the listening socket is already bound (to the port the OS
Expand Down Expand Up @@ -236,10 +236,13 @@ waitForIdle lastping = do
(max lp (woke + browsePingSeconds + 5 - browseIdleSeconds), ())
waitForIdle lastping

-- | How long browse mode keeps serving after the last ping. The pages ping
-- every browsePingSeconds, so a few pings can go missing before this runs out.
-- | How long browse mode keeps serving after the last ping. The pages ask to
-- ping every browsePingSeconds, but browsers slow down the timers of pages in
-- background tabs, and Safari may let one run only every five minutes or so.
-- This must outlast that, or the server would exit under a page that is
-- still open.
browseIdleSeconds :: Double
browseIdleSeconds = 120
browseIdleSeconds = 15 * 60

-- | How often an open page pings, as set in hledger.js.
browsePingSeconds :: Double
Expand Down
2 changes: 1 addition & 1 deletion hledger-web/Hledger/Web/WebOptions.hs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ webflags =
[ flagNone
["serve-browse"]
(setboolopt "serve-browse")
(serveprefix ++ "serve the web UI and JSON API, and open a browser, and exit if inactive for 2m (default)")
(serveprefix ++ "serve the web UI and JSON API, and open a browser, and exit if inactive for 15m (default)")
, flagNone
["serve"]
(setboolopt "serve")
Expand Down
2 changes: 1 addition & 1 deletion hledger-web/hledger-web.m4.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ hledger-web can be run in three modes:
- `--serve-browse` mode (the default):
the app serves the web UI and JSON API,
and opens your default web browser to show the app if possible,
and exits automatically once no browser window has shown it for two minutes.
and exits automatically once no browser window has shown it for fifteen minutes.

- `--serve`: the app just serves the web UI and JSON API.

Expand Down
33 changes: 25 additions & 8 deletions hledger-web/static/hledger.js
Original file line number Diff line number Diff line change
Expand Up @@ -404,20 +404,37 @@ function registerChartSelect(ev, ranges) {
// BROWSE MODE

// In the default --serve-browse mode the server exits once no browser
// window has shown it for two minutes (serveAndBrowse in Main.hs). It
// knows a window is open because the page pings it: on load, then every
// 30 seconds. Pages are marked for this by defaultLayout in browse mode
// only; the server answers /_ping in that mode only. The ping goes to the
// page's own origin, whatever address the browser reached us at (the
// policy allows requests to our origin only), under the base url's path,
// in case a proxy in front of us expects one.
// window has shown it for fifteen minutes (serveAndBrowse in Main.hs). It
// knows a window is open because the page pings it: on load, every 30
// seconds, and whenever the page is shown again. Browsers run the timers of
// background tabs less often, so the pings from a hidden page can be minutes
// apart; the ping on showing makes up for that as soon as the page is seen.
// Pages are marked for this by defaultLayout in browse mode only; the server
// answers /_ping in that mode only. The ping goes to the page's own origin,
// whatever address the browser reached us at (the policy allows requests to
// our origin only), under the base url's path, in case a proxy in front of
// us expects one.
//
// A ping that can't reach the server means it has stopped, so the page shows
// the #server-stopped notice, and hides it again if a later ping gets through
// (eg after the server was restarted on the same address).
function browsePingInit() {
if (!document.body.hasAttribute('data-browse-mode')) { return; }
var base = new URL(document.hledgerWebBaseurl, document.baseURI);
var url = base.pathname.replace(/\/$/, '') + '/_ping';
var notice = document.getElementById('server-stopped');
var ping = function() {
fetch(url, { cache: 'no-store' }).catch(function() {});
fetch(url, { cache: 'no-store' }).then(
function() { if (notice) { notice.hidden = true; } },
function() { if (notice) { notice.hidden = false; } });
};
ping();
setInterval(ping, 30000);
document.addEventListener('visibilitychange', function() {
if (document.visibilityState === 'visible') { ping(); }
});
// A page restored from the back/forward cache didn't run meanwhile.
window.addEventListener('pageshow', function(e) {
if (e.persisted) { ping(); }
});
}
4 changes: 4 additions & 0 deletions hledger-web/templates/default-layout.hamlet
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ $if elem ViewPermission perms
})();

<div#main-content .col-xs-12.#{mainShowmd}.#{mainShowsm}>
$# Shown by hledger.js while the page can't reach the server.
$if browsemode
<div #server-stopped .alert.alert-warning role=alert hidden>
_{HMsg "hledger-web is no longer running. It exits after fifteen minutes with no browser window showing it, or when stopped. To continue, start it again and reload this page."}
$maybe m <- msg
<div #message .alert.alert-info>#{m}
$if elem ViewPermission perms
Expand Down
8 changes: 4 additions & 4 deletions hledger-web/test/browser/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ highlighting.
- `helpers.js`, `server.js` — shared by the specs: collecting policy violations and page
errors, and starting hledger-web.
- `browse-mode.spec.js` — the default mode (no `--serve`), where each page pings
the server while it is open so that it keeps serving. This spec starts a second
hledger-web, with `--port 0` and the browser launcher stubbed, and checks that
the browser is opened at the port the OS chose; it is skipped on Windows, where
the launch cannot be intercepted.
the server while it is open so that it keeps serving, and says so when it can't
reach the server. This spec starts a second hledger-web, with `--port 0` and the
browser launcher stubbed, and checks that the browser is opened at the port the
OS chose; it is skipped on Windows, where the launch cannot be intercepted.

Nothing here is part of `stack build` or `stack test`; the suite is opt-in and needs
node only to run it.
Expand Down
27 changes: 21 additions & 6 deletions hledger-web/test/browser/browse-mode.spec.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// The default mode, with no --serve flag, opens a browser on hledger-web and
// exits the server once no browser window has shown it for two minutes. The
// page tells the server it is open by pinging /_ping, from hledger.js, on
// load and then periodically (serveAndBrowse in Main.hs). This spec checks
// that the page is marked for the ping in this mode, that the ping goes out
// and is answered, and that it does so without any policy violation. It also
// checks that with --port 0 the browser is opened at the port the OS chose.
// exits the server once no browser window has shown it for fifteen minutes.
// The page tells the server it is open by pinging /_ping, from hledger.js, on
// load, periodically, and when it is shown again (serveAndBrowse in Main.hs).
// This spec checks that the page is marked for the ping in this mode, that
// the ping goes out and is answered, and that it does so without any policy
// violation; that the page says so when it can't reach the server; and that
// with --port 0 the browser is opened at the port the OS chose.
//
// It starts its own hledger-web with --port 0, learning the url from the
// startup banner, and with the browser launcher stubbed out: hledger opens
Expand Down Expand Up @@ -58,6 +59,20 @@ test('in browse mode, the page pings the server so that it keeps serving', async
expect(pageErrors).toEqual([]);
});

test('the page says when it cannot reach the server, until it is shown again and can', async ({ page }) => {
const notice = page.locator('#server-stopped');
// as if the server had exited
await page.route('**/_ping', route => route.abort());
await page.goto(URL + '/journal');
await expect(notice).toBeVisible();

// as if it had been restarted; the next timed ping is 30s away, so it is
// the ping on showing the page that finds the server
await page.unroute('**/_ping');
await page.evaluate(() => document.dispatchEvent(new Event('visibilitychange')));
await expect(notice).toBeHidden();
});

test('with --port 0, the browser is opened at the port the OS chose', async () => {
expect(URL).not.toMatch(/:0$/);
// the launcher runs in the background once the server is listening
Expand Down