1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// SPDX-License-Identifier: 0BSD
import { extractValInfo } from "https://esm.town/v/pomdtr/extractValInfo?v=26";
import { gfm } from "https://esm.town/v/pomdtr/gfm?v=24";
import { appendFragment } from "https://esm.town/v/vladimyr/appendFragment";
import { fetchVal } from "https://esm.town/v/vladimyr/fetchVal";
import { linkifyReadme } from "https://esm.town/v/vladimyr/linkifyReadme";
export type HtmlProcessor = (html: string, author: string, name: string) => Promise<string>;
export function serveReadme(
valUrl: string | URL,
transformHtml: HtmlProcessor = decorateHtml,
) {
return async (): Promise<Response> => {
const { author, name } = extractValInfo(valUrl);
const { readme } = await fetchVal(author, name);
if (!readme) {
return new Response(null, { status: 404 });
}
let html = await readmeToHtml(readme, author, name);
html = await transformHtml(html, author, name);
return new Response(html, { headers: { "content-type": "text/html; charset=utf-8" } });
};
}
export { decorateHtml as transformHtml };
async function decorateHtml(html: string, author: string, name: string) {
html = await appendCodeOnValTownRibbon(html, author, name);
html = await appendPersonalizedFooter(html);
return html;
}
export async function appendCodeOnValTownRibbon(html: string, author: string, name: string) {
const { ribbonElement } = await import("https://esm.town/v/andreterron/codeOnVT_ribbonElement?v=7");
const fragment = ribbonElement({ val: { handle: author, name } });
return appendFragment(fragment, html);
}
export async function appendPersonalizedFooter(html: string) {
const { MyFooter: createMyFooter } = await import("https://esm.town/v/vladimyr/MyFooter");
const style = `<style>
footer .logo-white,
footer .logo-black {
background: none;
}
/* hack to remove underline under logo */
footer a + a:hover {
text-decoration: none !important;
}
</style>`;
const footer = await createMyFooter();
const fragment = style + footer;
return appendFragment(fragment, html);
}
async function readmeToHtml(readme: string, author: string, name: string) {
return linkifyReadme(
await gfm(readme, {
title: `${name} | @${author} | Val Town`,
favicon: new URL("https://www.val.town/favicon.svg"),
}),
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import { Hono } from "https://deno.land/x/hono@v4.0.6/mod.ts";
import { extractValInfo } from "https://esm.town/v/pomdtr/extractValInfo?v=25";
import { htmlResponse } from "https://esm.town/v/postpostscript/html";
import { html } from "https://esm.town/v/postpostscript/htmlComponentLibrary";
import { Layout } from "https://esm.town/v/postpostscript/Layout";
import { MyFooter } from "https://esm.town/v/postpostscript/MyFooter";
import { readmeMarkdown, readmeMarkdownPage } from "https://esm.town/v/postpostscript/readmeMarkdown";
const app = new Hono();
const { author, slug, httpEndpoint } = extractValInfo(import.meta.url);
app.get("/", (c) => {
return app.fetch(new Request(`${httpEndpoint}/blog`));
});
app.get("/:name", async (c) => {
const url = `@${author}/${c.req.param("name")}`;
const [md, footer] = await Promise.all([
readmeMarkdown(url),
MyFooter(),
]);
let index;
if (url !== "@postpostscript/blog") {
index = html`<p><a href="/">back to index</a></p>`;
}
return htmlResponse`
${
Layout({
title: `${url}`,
val: {
handle: author,
name: c.req.param("name"),
},
}, {
default: () =>
html`<main>${index}${md}<p><a href="https://postpostscript-readmemanager.web.val.run/${
url.slice(1)
}">view page source</a></p><br></main>${footer}`,
})
}
`;
});
export default app.fetch;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import {
authMiddlewareCookie,
type HonoEnvOptional,
userActionsDisplay,
} from "https://esm.town/v/postpostscript/authMiddleware";
import { html, type RawHTML } from "https://esm.town/v/postpostscript/html";
import { Layout } from "https://esm.town/v/postpostscript/Layout";
import { MyFooter } from "https://esm.town/v/postpostscript/MyFooter";
import { Profile } from "https://esm.town/v/postpostscript/ProfileImage";
import { Statement } from "https://esm.town/v/postpostscript/sqliteBuilder";
import { email } from "https://esm.town/v/std/email?v=11";
import { sqlite } from "https://esm.town/v/std/sqlite?v=4";
import { type Context, Hono } from "npm:hono";
const app = new Hono<HonoEnvOptional>();
app.use(
"*",
authMiddlewareCookie({
title: "Comments",
optional: true,
verify: {
issuer: undefined,
},
}),
);
app.get("/", (c) => createPage(c));
app.post("/", async (c) => {
if (!c.get("auth")) {
return c.redirect("/login");
}
const comment = await c.req.formData().then(data => data.get("comment")) as string;
await sqlite.batch([
SQL_CREATE_TABLE,
Statement`
INSERT INTO ${SQL_TABLE} VALUES (
${crypto.randomUUID()},
${c.get("auth").sub.replace(/^@/, "").split("/")[0]},
${comment},
${new Date().getTime() / 1000}
)
`,
]);
email({
subject: "New Comment on @postpostscript/authIdExampleComments",
html:
"New comment left on @postpostscript/authIdExampleComments: https://postpostscript-authidexamplecomments.web.val.run",
});
return createPage(c);
});
export default app.fetch;
async function createPage(c: Context<HonoEnvOptional>) {
const auth = c.get("auth");
const [
comments,
footer,
] = await Promise.all([
Statement`
SELECT *
FROM ${SQL_TABLE}
ORDER BY date_added DESC
`.execute({
fallback: () => [],
}),
MyFooter(),
]);
return c.html(Layout`
<header>
${auth && userActionsDisplay(auth)}
</header>
<main>
<h1>Comments</h1>
${auth ? commentForm : html`${signIn} to leave a comment`}
${
comments.map(({ username, comment, date_added }) => {
return html`
<div class="comment">
<span class="profile">
<a href="https://val.town/u/${username}" target="_blank" style="text-decoration: none; margin-right: 0;">
${Profile(username)}
</a>
</span>
<span class="username">
<a href="https://val.town/u/${username}" target="_blank" style="margin-right: 0;">
${username}
</a>
</span>
said at ${new Date(date_added * 1000).toLocaleString()} UTC: ${comment}
</div>
`;
})
}
<br>
</main>

authIdUserGuide: steps necessary to sign in as you to HTTP vals which support this pattern

Required Val: @[your username/authId

Required Val: @[your username/jwks

Setup

Test it out!

You are now on your sign in page that you completely control!

  • Click "Send Sign In Link to My Email"
  • Check for an email with the subject line "Sign In Request" and click the "Sign In" link
  • Review the permissions that the val has asked for. The only token scope that is currently required is "@[your username]/authId/id". If a val has requested any others, you may optionally accept them here
  • Click "Provide Access"

You are now signed in!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import { api } from "https://esm.town/v/pomdtr/api?v=9";
import {
authMiddlewareCookie,
signInFormBase,
userActionsDisplay,
} from "https://esm.town/v/postpostscript/authMiddleware";
import { html, htmlResponse, Layout } from "https://esm.town/v/postpostscript/htmlComponentLibrary";
import type { JWTPayload } from "https://esm.town/v/postpostscript/jwksUtils";
import { getValNameFromUrl } from "https://esm.town/v/postpostscript/meta";
import { MyFooter } from "https://esm.town/v/postpostscript/MyFooter";
import { readmeMarkdown } from "https://esm.town/v/postpostscript/readmeMarkdown";
import { type Context, Hono } from "npm:hono";
const app = new Hono();
const name = getValNameFromUrl(import.meta.url);
async function createPage(content: unknown) {
const [
footer,
readme,
] = await Promise.all([
MyFooter(),
readmeMarkdown(import.meta.url),
]);
return Layout`
<main>
${content}
<br>
<br>
<hr>
${readme}
<br>
</main>
${footer}
`.toString();
}
app.use(
"*",
authMiddlewareCookie<Context>({
verify: {
// by default this is `@[author of val]/authId`, meaning only the author can sign in
// setting it to undefined lets anyone sign in
issuer: undefined,
},
async createResponse(c, content, init) {
return c.html(createPage(content));
},
templates: {
signInForm: signInFormBase,
},
}),
);
app.get("/", async (c) => {
const payload = c.get("auth") as JWTPayload;
const userActions = userActionsDisplay(payload, {
order: ["language", "profile", "username", "emoji", "logout"],
parts: {
emoji: html`&nbsp;🎉`,
},
});
return c.html(createPage(userActions));
});
export default app.fetch;
1
Next