+17
-2
README.md
+17
-2
README.md
···
21
21
}
22
22
```
23
23
24
-
See `config.multiple.example.json` for an example of a multi-site config.
25
-
26
24
Then:
27
25
28
26
```bash
···
33
31
## Example
34
32
35
33
You can see an example of a hosted site [here](https://tangled-pages-example.gracekind.net).
34
+
35
+
## Configuration
36
+
37
+
See `config.multiple.example.json` for an example of a multi-site config.
38
+
39
+
If the repo is hosted on tangled.sh, you can use `tangledUrl` instead of specifying `ownerDid` and `repoName` directly.
40
+
(This is not recommended in workers since it requires an extra request to resolve the handle.)
41
+
42
+
E.g.
43
+
44
+
```json
45
+
{
46
+
"site": {
47
+
"tangledUrl": "https://tangled.sh/@gracekind.net/tangled-pages-example"
48
+
}
49
+
}
50
+
```
36
51
37
52
## Limitations
38
53
+8
config.multiple.example.json
+8
config.multiple.example.json
···
7
7
"branch": "main",
8
8
"baseDir": "/public",
9
9
"notFoundFilepath": "/404.html"
10
+
},
11
+
{
12
+
"subdomain": "url-example",
13
+
"tangledUrl": "https://tangled.sh/@gracekind.net/tangled-pages-example",
14
+
"tangledUrl:comment": "This will render the same site as above, but it's an example of how to use the tangledUrl field",
15
+
"branch": "main",
16
+
"baseDir": "/public",
17
+
"notFoundFilepath": "/404.html"
10
18
}
11
19
],
12
20
"subdomainOffset": 1,
+12
src/atproto.js
+12
src/atproto.js
···
10
10
return service.serviceEndpoint;
11
11
}
12
12
13
+
export async function resolveHandle(handle) {
14
+
const params = new URLSearchParams({
15
+
handle,
16
+
});
17
+
const res = await fetch(
18
+
"https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?" +
19
+
params.toString()
20
+
);
21
+
const data = await res.json();
22
+
return data.did;
23
+
}
24
+
13
25
async function resolveDid(did) {
14
26
if (did.startsWith("did:plc:")) {
15
27
const res = await fetch(`https://plc.directory/${encodeURIComponent(did)}`);
+30
-2
src/config.js
+30
-2
src/config.js
···
1
+
class SiteConfig {
2
+
constructor({
3
+
tangledUrl,
4
+
knotDomain,
5
+
ownerDid,
6
+
repoName,
7
+
branch,
8
+
baseDir,
9
+
notFoundFilepath,
10
+
}) {
11
+
if (tangledUrl) {
12
+
if ([ownerDid, repoName].some((v) => !!v)) {
13
+
throw new Error("Cannot use ownerDid and repoName with url");
14
+
}
15
+
}
16
+
this.tangledUrl = tangledUrl;
17
+
this.ownerDid = ownerDid;
18
+
this.repoName = repoName;
19
+
this.knotDomain = knotDomain;
20
+
this.branch = branch;
21
+
this.baseDir = baseDir;
22
+
this.notFoundFilepath = notFoundFilepath;
23
+
}
24
+
}
25
+
1
26
export class Config {
2
27
constructor({ site, sites, subdomainOffset, cache = false }) {
3
-
this.site = site;
4
-
this.sites = sites;
28
+
if (site && sites) {
29
+
throw new Error("Cannot use both site and sites in config");
30
+
}
31
+
this.site = site ? new SiteConfig(site) : null;
32
+
this.sites = sites ? sites.map((site) => new SiteConfig(site)) : null;
5
33
this.subdomainOffset = subdomainOffset;
6
34
this.cache = cache;
7
35
}
+32
-14
src/handler.js
+32
-14
src/handler.js
···
1
1
import { PagesService } from "./pages-service.js";
2
2
import { KnotEventListener } from "./knot-event-listener.js";
3
-
import { listRecords } from "./atproto.js";
3
+
import { listRecords, resolveHandle } from "./atproto.js";
4
4
5
5
async function getKnotDomain(did, repoName) {
6
6
const repos = await listRecords({
···
14
14
return repo.value.knot;
15
15
}
16
16
17
+
function parseTangledUrl(tangledUrl) {
18
+
// e.g. https://tangled.sh/@gracekind.net/tangled-pages-example
19
+
const regex = /^https:\/\/tangled\.sh\/@(.+)\/(.+)$/;
20
+
const match = tangledUrl.match(regex);
21
+
if (!match) {
22
+
throw new Error(`Invalid tangled URL: ${tangledUrl}`);
23
+
}
24
+
return {
25
+
handle: match[1],
26
+
repoName: match[2],
27
+
};
28
+
}
29
+
17
30
async function getPagesServiceForSite(siteOptions, config) {
31
+
// Fetch repoName and ownerDid if needed
32
+
let ownerDid = siteOptions.ownerDid;
33
+
let repoName = siteOptions.repoName;
34
+
35
+
if (siteOptions.tangledUrl) {
36
+
const { handle, repoName: parsedRepoName } = parseTangledUrl(
37
+
siteOptions.tangledUrl
38
+
);
39
+
console.log("Getting ownerDid for", handle);
40
+
const did = await resolveHandle(handle);
41
+
ownerDid = did;
42
+
repoName = parsedRepoName;
43
+
}
44
+
// Fetch knot domain if needed
18
45
let knotDomain = siteOptions.knotDomain;
19
46
if (!knotDomain) {
20
-
console.log(
21
-
"Getting knot domain for",
22
-
siteOptions.ownerDid + "/" + siteOptions.repoName
23
-
);
24
-
knotDomain = await getKnotDomain(
25
-
siteOptions.ownerDid,
26
-
siteOptions.repoName
27
-
);
47
+
console.log("Getting knot domain for", ownerDid + "/" + repoName);
48
+
knotDomain = await getKnotDomain(ownerDid, repoName);
28
49
}
29
50
return new PagesService({
30
51
knotDomain,
31
-
ownerDid: siteOptions.ownerDid,
32
-
repoName: siteOptions.repoName,
52
+
ownerDid,
53
+
repoName,
33
54
branch: siteOptions.branch,
34
55
baseDir: siteOptions.baseDir,
35
56
notFoundFilepath: siteOptions.notFoundFilepath,
···
38
59
}
39
60
40
61
async function getPagesServiceMap(config) {
41
-
if (config.site && config.sites) {
42
-
throw new Error("Cannot use both site and sites in config");
43
-
}
44
62
const pagesServiceMap = {};
45
63
if (config.site) {
46
64
pagesServiceMap[""] = await getPagesServiceForSite(config.site, config);