Avatar

pomdtr

I mainly enjoy tinkering with the val.town api: - VS Code integration: https://github.com/pomdtr/valtown-vscode - CLI: https://github.com/pomdtr/vt
276 public vals
Joined June 14, 2023

Val Town Basic Auth

Add basic auth on top of any http val

Usage

Wrap your HTTP handler in the basicAuth middleware.

import { basicAuth } from "https://esm.town/v/pomdtr/basicAuth";

function handler(req: Request) {
  return new Response("You are authenticated!");
}

export default basicAuth(handler);

To authenticate, paste an api token in the password prompt.

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
function extractToken(authorization) {
const parts = authorization.split(" ");
if (parts[0] == "Bearer") {
return parts[1];
}
if (parts[0] != "Basic") {
return "";
}
const plainAuth = atob(parts[1]);
const credentials = plainAuth.split(":");
// allow `curl <token>@xxx.web.val.run`
return credentials[1] || credentials[0];
}
type User = {
id: string;
};
async function fetchUser(token: string): Promise<User> {
const resp = await fetch("https://api.val.town/v1/me", {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (resp.status !== 200) {
throw new Error("Could not fetch user");
}
return resp.json();
}
async function isTokenValid(token) {
try {
const [visitor, owner] = await Promise.all([fetchUser(token), fetchUser(Deno.env.get("valtown"))]);
return visitor.id == owner.id;
} catch (err) {
return false;
}
}
async function isRequestAuthenticated(req) {
if (!req.headers.has("authorization")) {
return false;
}
const token = extractToken(req.headers.get("authorization"));
return isTokenValid(token);
}
export function basicAuth(next: (Request) => Response | Promise<Response>) {
return async (req: Request) => {
if (req.headers.get("referer") == "https://www.val.town/") {
return new Response(
`Basic Auth is disabled in Val Town iframes.
<a href="/" target="blank_">Open in a new tab.</a>`,
{
status: 400,
headers: {
"Content-type": "text/html",
},
},
);
}
const isAuth = await isRequestAuthenticated(req);
if (!isAuth) {
return new Response("Unauthorized", {
status: 401,
headers: {
"WWW-Authenticate": "Basic",
},
});
}
return next(req);
};
}

Lowdb Example

This val demonstrates the integration between valtown and lowdb.

Read the Lodash section if you want to give superpowers to your DB.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { extractValInfo } from "https://esm.town/v/pomdtr/extractValInfo";
import { BlobPreset } from "https://esm.town/v/pomdtr/lowdb";
type Data = {
posts: {
id: number;
title: string;
}[];
};
// use the val name as a key ("@pomdtr/testLowDB")
const { slug } = extractValInfo(import.meta.url);
// Read or initialize DB from blob
const db = await BlobPreset<Data>(slug, { posts: [] });
// Edit db content using plain JavaScript (updates are automatically persisted)
await db.update(({ posts }) => posts.push({ id: posts.length + 1, title: "lowdb is awesome" }));
console.log(db.data);

Augmented run api

This val is a wrapper on top of the val.town run api, improving it with additional features:

  • basic auth
  • content-type header in response based on url file extension

Usage

Custom Content-Type

The content-type will be inferred from the filename using the mime-types library.

If you use a .html extension, the response will be interpreted as text/html

~ $ curl -v 'https://pomdtr-run.web.val.run/pomdtr/helloWorld.html'
HTTP/1.1 200 OK
...
Content-Type: text/html; charset=utf-8
...

Hello, World!

If you switch the extension to .txt, the content-type header switch to text/raw.

~ $ curl -v 'https://pomdtr-run.web.val.run/pomdtr/helloWorld.txt'
HTTP/1.1 200 OK
...
Content-Type: text/plain; charset=utf-8
...

Hello, World!

Passing arguments

The request is proxyed to the run api, so you can pass args to your vals via query params or body. See the run api docs for more details.

~ $ curl -X POST -d '{"args": ["pomdtr"]}' 'https://pomdtr-run.web.val.run/pomdtr/helloWorld.html'
...
< content-type: text/html; charset=utf-8
...

Hello, pomdtr!

Basic Authentication

Just add your val town token as the username:

curl  'https://<val-token>@pomdtr-run.web.val.run/pomdtr/privateVal.txt'
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
import { fetch } from "https://esm.town/v/std/fetch";
import { parseAuthorizationHeader } from "https://esm.town/v/pomdtr/parseAuthorizationHeader";
export async function run(req: Request) {
const url = new URL(req.url);
const [owner, filename] = url.pathname.slice(1).split("/");
const [name] = filename.split(".");
const mime = await import("npm:mime-types");
let contentType = await mime.lookup(filename);
if (!contentType) {
contentType = "application/json";
}
url.host = "api.val.town";
url.pathname = `v1/run/${owner}.${name}`;
const body = await req.text();
const headers = {};
const authorization = req.headers.get("Authorization");
if (authorization) {
const auth = parseAuthorizationHeader(
req.headers.get("Authorization"),
);
if (auth.type == "basic") {
headers["Authorization"] = `Bearer ${auth.username}`;
}
else {
headers["Authorization"] = req.headers.get("Authorization");
}
}
const resp = await fetch(url.toString(), {
method: req.method,
body: body || undefined,
headers,
});
if (resp.status != 200) {
return resp;
}
if (contentType == "application/json") {
return resp;
}
const res = await resp.json();
return new Response(typeof res == "string" ? res : JSON.stringify(res), {
headers: { "Content-Type": contentType },
});
}

Fetch the source of a val

This val was created before the introduction of https://esm.town

Usage

curl https://pomdtr-raw.web.val.run/<author>/<name>.<extension>[?v=<version>]

To see the code of this val, use https://pomdtr-raw.web.val.run/pomdtr/raw.ts

Examples

Fetching the val code

$  curl https://pomdtr-raw.web.val.run/pomdtr/add.tsx

You can also use js, jsx and ts extension (only the content-type change, there is no transpilation).

Fetching private val

Pass an api token as an username

$ curl "https://<token>@pomdtr-raw.web.val.run/pomdtr/privateVal.ts"

Fetching the val README

$ curl https://pomdtr-raw.web.val.run/pomdtr/add.md

Getting an image

$ curl https://pomdtr-raw.web.val.run/pomdtr/add.png

Fetching a specific version of a val

$ curl https://pomdtr-raw.web.val.run/pomdtr/raw.ts?v=66

You need to be authenticated to use this method.

Fetching the val metadata

$ curl https://pomdtr-raw.web.val.run/pomdtr/add.json

Running vals locally using Deno

Create a new val.ts file referencing the @pomdtr.add

import { add } from "https://pomdtr-raw.web.val.run/pomdtr/add.ts";

console.log(add(1, 2));

then use deno run

$ deno run ./val.ts
3

If you val accept a request and return a response, you can pass it to Deno.Serve to run it locally!

import {raw} from "https://pomdtr-raw.web.val.run/pomdtr/raw.ts";

Deno.serve(raw);

If your val is private, you can set the DENO_AUTH_TOKENS env.

DENO_AUTH_TOKENS=<val-town-token>@pomdtr-raw.web.val.run
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 { extractValInfo } from "https://esm.town/v/pomdtr/extractValInfo";
import { parseAuthorizationHeader } from "https://esm.town/v/pomdtr/parseAuthorizationHeader";
import { readmeToHtmlResponse } from "https://esm.town/v/pomdtr/readme";
async function createScreenshot(code: string) {
const apiUrl = "https://sourcecodeshots.com/api/image";
const resp = await fetch(apiUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ code }),
});
if (!resp.ok) {
throw new Error(await resp.text());
}
return resp.blob();
}
export default async function raw(req: Request) {
const url = new URL(req.url);
const [author, file] = url.pathname.slice(1).split("/");
if (!author || !file) {
const { author, name } = extractValInfo(import.meta.url);
return readmeToHtmlResponse(author, name);
}
const [name, extension] = file.split(".");
const headers = {};
if (req.headers.get("Authorization")) {
const auth = parseAuthorizationHeader(
req.headers.get("Authorization"),
);
if (auth.type == "basic") {
headers["Authorization"] = `Bearer ${auth.username}`;
}
else {
headers["Authorization"] = req.headers.get("Authorization");
}
}
const resp = await fetch(`https://api.val.town/v1/alias/${author}/${name}`, {
headers,
});
if (!resp.ok) {
return resp;
}
let val = await resp.json();
if (url.searchParams.has("v")) {
const version = url.searchParams.get("v");
const resp = await fetch(
`https://api.val.town/v1/vals/${val.id}/versions/${version}`,
{ headers },
);
if (!resp.ok) {
return resp;
}
val = await resp.json();
}
if (extension == "json") {
return new Response(JSON.stringify(val, null, 2), {
headers: { "Content-Type": "text/json" },
});
}
if (extension == "md") {
return new Response(val.readme, {
headers: { "content-type": "text/markdown" },
});
}
if (extension == "js") {
return new Response(val.code, {
headers: {
"Content-Type": "text/javascript",
},
});
}
if (extension == "ts") {
return new Response(val.code, {
headers: {
"Content-Type": "text/javascript",
},
});
}
if (extension == "jsx") {
return new Response(val.code, {
headers: {
"Content-Type": "text/jsx",
},
});
}
if (extension == "tsx") {
return new Response(val.code, {
headers: {
"Content-Type": "text/tsx",

Usage

![Button](https://pomdtr-badge.express.val.run/Custom%20Badge)

Button

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
import { fetch } from "https://esm.town/v/std/fetch";
export async function badge(req, res) {
async function inlineUrls(cssString) {
const urlRegex = /url\(['"]?(.*?)['"]?\)/g;
let result = cssString;
for (const urlMatch of cssString.matchAll(urlRegex)) {
const originalUrl = urlMatch[1];
const response = await fetch(originalUrl);
const data = new Uint8Array(await response.arrayBuffer());
result = result.replace(
urlMatch[0],
`url("data:${response.headers.get("content-type")};base64,${
btoa(data.reduce((a, b) => a + String.fromCharCode(b), ""))
}")`,
);
}
return result;
}
const esc = (await import("npm:escape-html@1.0.3")).default;
const { init } = await import("npm:server-text-width@1.0.2");
const { getTextWidth } = init(
(await import("https://easrng.github.io/IBMPlexSansMetrics.js")).default,
);
const text = decodeURIComponent(req.path.split("/")[1]);
let fontCss = "";
try {
const r = await fetch(
"https://fonts.googleapis.com/css2?family=" +
encodeURIComponent("IBM Plex Sans:wght@600") +
"&text=" +
encodeURIComponent(text),
{ headers: { "user-agent": req.get("user-agent") } },
);
if (!r.ok)
throw new Error();
fontCss = await inlineUrls(await r.text());
}
catch (e) {}
res.set("content-type", "image/svg+xml");
const width = Math.ceil(getTextWidth(text) + 16);
res.end(
`<svg viewBox="0 0 ${width} 28" width="${width}" height="28" xmlns="http://www.w3.org/2000/svg"><defs><style>${fontCss}</style></defs><foreignObject width="100%" height="100%"><span xmlns="http://www.w3.org/1999/xhtml" style="font-family: 'IBM Plex San
esc(text)
}</span></foreignObject></svg>`,
);
}
// Forked from @easrng.button

Serve prefixed blobs.

Usage

Create valimport { serveBlobs } from "https://esm.town/v/pomdtr/serve_blobs" export default serveBlobs({ root: "public/" })

All your blobs prefixed by public/ will be publicly accessible.

Ex: Go to https://pomdtr-public.web.val.run/example.json to view the blob public/example.json from my account.

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
import * as path from "jsr:@std/path";
import { blob } from "https://esm.town/v/std/blob?v=12";
import { ValTownBlobNotFoundError } from "https://esm.town/v/std/ValTownBlobNotFoundError";
import mime from "npm:mime";
export function serveBlobs({
root,
mimes = {},
}: {
root: string;
mimes?: Record<string, string>;
}) {
return async (req: Request) => {
const url = new URL(req.url);
const key = path.join(
root,
url.pathname == "/" ? "index.html" : url.pathname
);
try {
const res = await blob.get(key);
const extension = key.split(".").pop();
return new Response(res.body, {
headers: {
"Content-Type": mimes[extension] || mime.getType(key),
},
});
} catch (e) {
if (e instanceof ValTownBlobNotFoundError) {
return new Response(null, {
status: 404,
});
}
return new Response(null, {
status: 500,
});
}
};
}

Awesome Val Town

An curated list of useful community vals. Feel free to create your own awesome list!

Apps

Tooling

Authentication

Sqlite

Blob

Middleware

Testing

Api

Other

OpenAI

Web Components

1
2
3
4
5
import { serveReadme } from "https://esm.town/v/pomdtr/serve_readme";
import { extractValInfo } from "https://esm.town/v/pomdtr/extractValInfo";
const val = extractValInfo(import.meta.url)
export default serveReadme({val, title: "Awesome Val Town"})

SSR + Hydration Demo

Look at @pomdtr/island and @pomdtr/hydrate_islands to read the whole library source code (less than 50 rows!).

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
/** @jsxImportSource https://esm.sh/react */
import { useEffect, useState } from "https://esm.sh/react";
// will run on both client and server
export function Demo(props: { items: string[] }) {
const [items, setItems] = useState(props.items);
useEffect(() => {
items.push("This item is rendered only on the client, after the hydration");
setItems([...items]);
// fetch data from the server
fetch("/api/item")
.then((res) => res.json())
.then((item) => setItems([...items, item]));
}, []);
return (
<div className="flex h-full justify-center items-center font-mono">
<ol className="list-decimal">
{items.map((item) => (
<li key={item}>{item}</li>
))}
</ol>
</div>
);
}
// You server code should be contained in this function
export default async function (req: Request) {
const url = new URL(req.url);
if (url.pathname == "/api/item") {
return Response.json("This item is fetched from the server by the client");
}
// server-side import should only be used here
const { Island } = await import("https://esm.town/v/pomdtr/island");
const { renderToString } = await import("https://esm.sh/react-dom/server");
return new Response(
renderToString(
<html>
<head>
<script src="https://cdn.tailwindcss.com"></script>
<script
src="https://esm.town/v/pomdtr/hydrate_islands"
type="module"
defer
></script>
</head>
<body className="h-screen">
<Island src={import.meta.url} name={Demo.name}>
<Demo
items={[
"This item is rendered both on the server and the client",
]}
/>
</Island>
</body>
</html>
),
{ headers: { "Content-Type": "text/html" } }
);
}
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
const html = `
<html>
<head>
<style>body { margin: 0px }</style>
<script type="module" src="https://cdn.jsdelivr.net/gh/vanillawc/wc-codemirror@1/index.js"></script>
<script type="module" src="https://cdn.jsdelivr.net/gh/vanillawc/wc-codemirror@1/mode/javascript/javascript.js"></script>
</head>
<body>
<wc-codemirror mode="javascript">
<script type="wc-content" >
console.log("Hello World!")
</script>
</wc-codemirror>
</body>
</html>`;
export default function(req: Request) {
return new Response(
html,
{
headers: {
"Content-Type": "text/html",
},
},
);
}

Email Auth for Val Town

⚠️ Require a pro account (needed to send email to users)

Usage

Create an http server, and wrap it in the emailAuth middleware.

Create valimport { emailAuth } from "https://www.val.town/v/pomdtr/email_auth" export default emailAuth((req, ctx) => { return new Response(`your mail is ${ctx.email}`); });

When an user access the val, he will need to input his mail, then confirm it through a confirmation code.

You can limit how can access your vals through an allowList:

Create valimport { emailAuth } from "https://www.val.town/v/pomdtr/email_auth" export default emailAuth((req, ctx) => { return new Response(`your mail is ${ctx.email}`); }, { allowList: ["steve@val.town"] });

If someone tries to access your val but is not in the allowlist, he will be blocked.

If you want to allow user to request for access, you can mix allowList with allowSignup:

Create valimport { emailAuth } from "https://www.val.town/v/pomdtr/email_auth" export default emailAuth((req, ctx) => { return new Response(`your mail is ${ctx.email}`); }, { allowList: ["steve@val.town"], allowSignup: true });

Each time a new user not present in the allowList try to login to a val, you will receive an email containing:

  • the email of the user trying to log in
  • the name of the val the he want to access

You can then just add the user to your whitelist to allow him in (and the user will not need to confirm his email again) !

Tips

If you don't want to put your email in clear text, you can just use an env variable:

Create valimport { emailAuth } from "https://www.val.town/v/pomdtr/email_auth" export default emailAuth((req, ctx) => { return new Response(`your mail is ${ctx.email}`); }, { allowList: [Deno.env.get("email")] });

Or just setup a forward val (see @pomdtr/inbox):

Create valimport { emailAuth } from "https://www.val.town/v/pomdtr/email_auth" export default emailAuth((req, ctx) => { return new Response(`your mail is ${ctx.email}`); }, { allowList: ["pomdtr.inbox@valtown.email"] });

TODO

  • Add expiration for verification codes and session tokens
  • use links instead of code for verification
  • improve errors pages
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 { deleteCookie, getCookies, setCookie } from "https://deno.land/std/http/cookie.ts";
import { inferRequestVal } from "https://esm.town/v/andreterron/inferRequestVal?v=2";
import { email as sendEmail } from "https://esm.town/v/std/email?v=11";
import { sqlite } from "https://esm.town/v/std/sqlite?v=4";
import { html } from "https://esm.town/v/stevekrouse/html?v=5";
import { zip } from "npm:lodash-es";
import { nanoid } from "npm:nanoid";
import { createDate, TimeSpan } from "npm:oslo";
import { alphabet, generateRandomString } from "npm:oslo/crypto";
type Session = {
id: string;
email: string;
expiresAt: number;
};
async function createSessionTable(sessionTableName: string) {
await sqlite.execute(`CREATE TABLE ${sessionTableName} (
id TEXT NOT NULL PRIMARY KEY,
expires_at INTEGER NOT NULL,
email STRING NOT NULL,
val_slug STRING NOT NULL
);`);
}
async function createCodeTable(tableName: string) {
await sqlite.execute(`CREATE TABLE ${tableName} (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
code STRING NOT NULL,
email STRING NOT NULL,
expires_at INTEGER NOT NULL
)`);
}
async function generateEmailVerificationCode(tableName, email: string): Promise<string> {
try {
await sqlite.execute({ sql: `DELETE FROM ${tableName} WHERE email = ?`, args: [email] });
const code = generateRandomString(8, alphabet("0-9"));
const expires_at = createDate(new TimeSpan(5, "m")); // 5 minutes
await sqlite.execute({
sql: `INSERT INTO ${tableName} (email, code, expires_at) VALUES (?, ?, ?)`,
args: [email, code, expires_at.getTime() / 1000],
});
return code;
} catch (e) {
if (e.message.includes("no such table")) {
await createCodeTable(tableName);
return generateEmailVerificationCode(tableName, email);
}
throw e;
}
}
async function createSession(tableName: string, valSlug: string, email: string): Promise<Session> {
try {
const expiresAt = new Date();
expiresAt.setDate(expiresAt.getDate() + 7);
const sessionID = nanoid();
await sqlite.execute({
sql: `INSERT INTO ${tableName} (id, val_slug, expires_at, email) VALUES (?, ?, ?, ?)`,
args: [sessionID, valSlug, expiresAt.getTime() / 1000, email],
});
return {
id: sessionID,
email,
expiresAt: expiresAt.getTime(),
};
} catch (e) {
if (e.message.includes("no such table")) {
await createSessionTable(tableName);
return createSession(tableName, valSlug, email);
}
throw e;
}
}
async function getSession(tableName: string, sessionID: string, valSlug: string): Promise<Session> {
try {
const { rows, columns } = await sqlite.execute({
sql: `SELECT * FROM ${tableName} WHERE id = ? AND val_slug = ?`,
args: [sessionID, valSlug],
});
if (rows.length == 0) {
return null;
}
return Object.fromEntries(zip(columns, rows.at(0))) as Session;
} catch (e) {
if (e.message.includes("no such table")) {
return null;
}
throw e;
}
}