diff --git a/README.md b/README.md index 42d26af..861c09e 100644 --- a/README.md +++ b/README.md @@ -307,7 +307,8 @@ uvx xbrlkit view NVDA A browser cannot be handed a local path — `file://` is unreachable from an https page, and a file input cannot be pre-populated — so this serves the document instead, on an ephemeral loopback port with an unguessable path, and -opens `xbrlkit.com/?url=…` pointing at it. `http://127.0.0.1` is a +opens `xbrlkit.com/view?url=…` pointing at it (earlier releases open +`xbrlkit.com/?url=…`, which keeps working). `http://127.0.0.1` is a potentially trustworthy origin, so the https page may read it; the CORS header names the viewer's origin and no other. The document is readable there, by that origin, until you press Ctrl-C. `--viewer` points at a different build. diff --git a/tests/test_serve.py b/tests/test_serve.py index b075658..aa544ab 100644 --- a/tests/test_serve.py +++ b/tests/test_serve.py @@ -755,7 +755,7 @@ def test_view_filing_serves_it_for_the_viewer(loaded: LoadedFiling) -> None: try: out = tools.view_filing(loaded, "holon", viewers) assert out["filing"] == loaded.id and out["bytes"] > 0 - assert out["viewer_url"].startswith(f"{DEFAULT_VIEWER}/?url=") + assert out["viewer_url"].startswith(f"{DEFAULT_VIEWER}/view?url=") assert out["document_url"] in out["viewer_url"] assert out["document_url"].startswith("http://127.0.0.1:") with pytest.raises(tools.ToolError): diff --git a/tests/test_view.py b/tests/test_view.py index 1bb273f..afa8271 100644 --- a/tests/test_view.py +++ b/tests/test_view.py @@ -20,6 +20,7 @@ from xbrlkit.view import ( DEFAULT_VIEWER, + VIEWER_PAGE, ViewerHost, origin_of, serve_report, @@ -71,13 +72,22 @@ def test_the_path_carries_an_unguessable_token(served) -> None: def test_the_viewer_url_names_the_document(served) -> None: - assert served.viewer_url.startswith(f"{DEFAULT_VIEWER}/?url=") + assert served.viewer_url.startswith(f"{DEFAULT_VIEWER}/view?url=") assert served.file_url in served.viewer_url +def test_the_link_opens_the_viewers_view_page() -> None: + # `/view`, not `/`: the viewer's analytics see the path and drop the query, + # so the path is what tells an open from xbrlkit apart from a web link. + assert VIEWER_PAGE == "view" + assert viewer_url(DEFAULT_VIEWER, "http://127.0.0.1:1/t/a.json") == ( + "https://xbrlkit.com/view?url=http://127.0.0.1:1/t/a.json" + ) + + def test_viewer_url_and_origin_helpers() -> None: assert viewer_url("https://example.test/", "http://127.0.0.1:1/a.json") == ( - "https://example.test/?url=http://127.0.0.1:1/a.json" + "https://example.test/view?url=http://127.0.0.1:1/a.json" ) assert origin_of("https://example.test/path?q=1") == "https://example.test" with pytest.raises(ValueError): diff --git a/xbrlkit/view.py b/xbrlkit/view.py index 60b906d..3ecee2f 100644 --- a/xbrlkit/view.py +++ b/xbrlkit/view.py @@ -17,6 +17,12 @@ The only machinery this needs, then, is a CORS header, because the viewer's origin is not this one. +**The link.** It opens the viewer's ``/view`` page, ``https://xbrlkit.com/view?url=…``. +The viewer reads ``/view`` and ``/`` the same way: a ``?url=`` on either opens the +document. The path is what separates an open from here from a report link on the +web, because the viewer's page-view analytics record the path and drop the query +string. Earlier releases open ``/?url=…``, which the viewer keeps serving for good. + **Posture.** Loopback only, one document per path, and each path carries an unguessable token — so the document is readable by the viewer origin, for as long as the process runs, and not by every other page the browser has open. @@ -40,6 +46,10 @@ # origin, so the old name must keep working for those installs. DEFAULT_VIEWER = "https://xbrlkit.com" +# The viewer page a link from xbrlkit opens: `/view` rather than `/`, so the +# viewer can count these opens apart from report links on the web. +VIEWER_PAGE = "view" + _CONTENT_TYPES = { ".jsonld": "application/ld+json", ".json": "application/json", @@ -55,8 +65,8 @@ def origin_of(url: str) -> str: def viewer_url(viewer: str, file_url: str) -> str: - """The viewer page that opens ``file_url`` — the ``?url=`` link.""" - return f"{viewer.rstrip('/')}/?url={quote(file_url, safe=':/')}" + """The viewer's ``/view`` page, opening ``file_url`` — the ``?url=`` link.""" + return f"{viewer.rstrip('/')}/{VIEWER_PAGE}?url={quote(file_url, safe=':/')}" class _Handler(BaseHTTPRequestHandler):