diff options
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/api/webapp.py | 33 | ||||
| -rw-r--r-- | packages/meshbay-hub/tests/test_asset_versioning.py | 5 | ||||
| -rw-r--r-- | packages/meshbay-hub/tests/test_site_basics.py | 33 |
3 files changed, 67 insertions, 4 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/api/webapp.py b/packages/meshbay-hub/src/meshbay_hub/api/webapp.py index c9cb8d5..1d3bc7e 100644 --- a/packages/meshbay-hub/src/meshbay_hub/api/webapp.py +++ b/packages/meshbay-hub/src/meshbay_hub/api/webapp.py @@ -164,11 +164,20 @@ async def index(): # square icon, which is the shape a thumbnail is cut to anyway. PREVIEW_DESCRIPTION = "Your files stay at home. Reach them from anywhere." +# What a search engine shows under the link: two lines, about 150 characters, +# so it can say what the preview has no room for. welcome.lead, shortened. +SEARCH_TITLE = "MeshBay — private, encrypted sharing from your own computer" +SEARCH_DESCRIPTION = ("Your photos, music, videos and conversations stay on your " + "own computer. MeshBay lets you reach them from anywhere " + "and share them with the people you choose.") + def _preview_tags(hub_id: str) -> str: origin = html.escape(f"https://{hub_id}", quote=True) return f"""\ - <meta name="description" content="{PREVIEW_DESCRIPTION}"> + <meta name="description" content="{SEARCH_DESCRIPTION}"> + <!-- "/" and every /app/* path serve this same shell: one URL for all. --> + <link rel="canonical" href="{origin}/"> <meta property="og:type" content="website"> <meta property="og:site_name" content="MeshBay"> <meta property="og:title" content="MeshBay"> @@ -203,13 +212,31 @@ _HTML = """\ Ignored by browsers that do not know it. --> <meta name="viewport" content="width=device-width, initial-scale=1, interactive-widget=resizes-content"> - <title>MeshBay</title> + <title>{title}</title> {preview} <link rel="icon" href="/a/{v}/favicon.ico" sizes="16x16 32x32 48x48"> <link rel="apple-touch-icon" href="/a/{v}/apple-touch-icon.png"> <link rel="stylesheet" href="/a/{v}/style.css"> </head> <body> <div id="app"></div> + <!-- The only text in this page before app.js runs: what a crawler that does + not execute scripts reads and follows, and what a browser with scripts + off shows instead of a blank page. The links are the welcome page's + (auth-page.js), pointing at the project's site like it does. --> + <noscript> + <h1>MeshBay</h1> + <p>Your photos, music, videos and conversations stay at home, on your own + computer. MeshBay lets you enjoy them from anywhere, and share them with + the people you choose.</p> + <p>Signing in needs JavaScript, which is turned off in this browser.</p> + <ul> + <li><a href="https://meshbay.org/downloads/">Download (beta)</a></li> + <li><a href="https://git.meshbay.org/meshbay.git/about/docs/QUICKSTART.md">Quick start</a></li> + <li><a href="https://git.meshbay.org/meshbay.git/about/docs/USERGUIDE.md">User guide</a></li> + <li><a href="https://git.meshbay.org/meshbay.git/about/">Source code</a></li> + <li><a href="https://meshbay.org/legal/">Legal information</a></li> + </ul> + </noscript> <!-- Everything below is loaded from a path that carries the fingerprint of what we are serving, so a browser holding a heuristically-cached copy of an older build fetches this one instead of deciding it need not ask — @@ -239,6 +266,6 @@ _HTML = """\ <script type="module" src="/a/{v}/app.js"></script> </body> </html> -""".replace("{v}", ASSET_V) +""".replace("{v}", ASSET_V).replace("{title}", html.escape(SEARCH_TITLE)) _shell = _HTML.replace("{preview}", _preview_tags("meshbay.org")) diff --git a/packages/meshbay-hub/tests/test_asset_versioning.py b/packages/meshbay-hub/tests/test_asset_versioning.py index 7057311..2d90a51 100644 --- a/packages/meshbay-hub/tests/test_asset_versioning.py +++ b/packages/meshbay-hub/tests/test_asset_versioning.py @@ -32,7 +32,10 @@ def client(): def _shell_refs(html: str) -> list[str]: - return re.findall(r'(?:src|href)="([^"]+)"', html) + """What the shell loads: script sources and <link> files. Not the canonical + link nor the <noscript> anchors, which name pages rather than fetch them.""" + return (re.findall(r'src="([^"]+)"', html) + + re.findall(r'<link rel="(?!canonical")[^"]+" href="([^"]+)"', html)) def test_every_asset_the_shell_loads_is_versioned(client): diff --git a/packages/meshbay-hub/tests/test_site_basics.py b/packages/meshbay-hub/tests/test_site_basics.py index 5c19aa6..40508a1 100644 --- a/packages/meshbay-hub/tests/test_site_basics.py +++ b/packages/meshbay-hub/tests/test_site_basics.py @@ -93,3 +93,36 @@ def test_the_preview_description_fits_in_a_preview(): nothing.""" from meshbay_hub.api.webapp import PREVIEW_DESCRIPTION assert len(PREVIEW_DESCRIPTION) <= 60 + + +def test_search_engines_get_a_title_and_description_that_say_what_it_is(client): + """The preview description is cut for messengers; a search result has + room for two lines, and a bare "MeshBay" title matches no search.""" + from meshbay_hub.api.webapp import SEARCH_DESCRIPTION, SEARCH_TITLE + html = client.get("/").text + assert f"<title>{SEARCH_TITLE}</title>" in html + assert 50 <= len(SEARCH_TITLE) <= 60 + assert re.search(r'<meta name="description" content="([^"]*)">', html).group(1) == SEARCH_DESCRIPTION + assert 110 <= len(SEARCH_DESCRIPTION) <= 160 + + +def test_every_path_to_the_shell_names_the_root_as_canonical(): + from meshbay_hub.config import load_config + cfg = load_config() + cfg.identity.id = "hub.example.org" + client = TestClient(create_app(cfg)) + for path in ("/", "/app", "/app/groups"): + html = client.get(path).text + assert '<link rel="canonical" href="https://hub.example.org/">' in html, path + + +def test_the_shell_has_text_and_links_without_scripts(client): + """A crawler that does not run app.js reads the <noscript> block and + nothing else, so it must carry the pitch and the links to follow.""" + html = client.get("/").text + m = re.search(r"<noscript>(.*?)</noscript>", html, re.S) + assert m, "no <noscript> block" + links = re.findall(r'href="([^"]+)"', m.group(1)) + assert "https://meshbay.org/downloads/" in links + assert any(link.endswith("docs/QUICKSTART.md") for link in links) + assert "<h1>" in m.group(1) |