~james/core

ref: 452f17b75181d62d1d92016edecd38e1d70fecab core/web/src/lib/server/gitHttp.ts -rw-r--r-- 2.9 KiB
452f17b7 — james chore(import): snapshot james.dad.town/core at f70c71c7696a 16 hours ago
                                                                                
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
import { isDid } from "@atcute/lexicons/syntax";
import { error } from "@sveltejs/kit";
import { serviceUrlFor, tryHostForKnot } from "#lib/api/_request.ts";
import { createBobbinClient } from "#lib/api/client.ts";
import { httpStatusFor, toHttpError } from "#lib/api/load.ts";
import { getRepoByRepoDid, type RepoView } from "#lib/api/records.ts";
import { didFromUri } from "#lib/api/uri.ts";
import { getConfig } from "#lib/server/config.ts";
import { capture } from "#lib/server/posthog.ts";
import { resolveRepoViewFromParams } from "#lib/server/repo.ts";
import type { Fetch } from "#lib/fetch.ts";
interface GitEvent<Params> {
	params: Params;
	url: URL;
	fetch: Fetch;
	platform?: App.Platform;
}

// git follows this once on info/refs and sends the rest of the clone or push
// straight to the knot
const knotRedirect = (view: RepoView, repoDid: string, service: string, url: URL) => {
	const host = tryHostForKnot(view.knot);
	if (!host) error(404, "This repository doesn't have a knot yet");
	return new Response(null, {
		status: 307,
		headers: { location: `${serviceUrlFor(host)}/${repoDid}/${service}${url.search}` }
	});
};

// the knot serves the clone itself, so its ref advertisement here is the one place
// we see it. fetches and pulls ask for the same thing, as they did on the appview
const trackClone = (event: GitEvent<unknown>, view: RepoView, repoDid: string, service: string) => {
	if (service !== "info/refs" || event.url.searchParams.get("service") !== "git-upload-pack")
		return;
	const declaration = view.value.declaration;
	const sent = capture(getConfig(event.platform), "clone", declaration?.owner ?? repoDid, {
		repo: declaration?.name,
		repo_at: view.uri
	});
	// on workers the event has to outlive the redirect it rides on
	event.platform?.ctx?.waitUntil(sent);
};

export const redirectToKnot =
	(service: string) =>
	async (event: GitEvent<{ handle: string; repo: string }>): Promise<Response> => {
		const { view } = await resolveRepoViewFromParams({
			params: { handle: event.params.handle, repo: event.params.repo.replace(/\.git$/, "") },
			fetch: event.fetch
		});
		const repoDid = didFromUri(view.uri);
		const response = knotRedirect(view, repoDid, service, event.url);
		trackClone(event, view, repoDid, service);
		return response;
	};

export const redirectRepoDidToKnot =
	(service: string) =>
	async (event: GitEvent<{ handle: string }>): Promise<Response> => {
		const repoDid = event.params.handle;
		if (!isDid(repoDid)) error(404, "Not found");
		const ctx = createBobbinClient({ serviceUrl: getConfig().bobbinUrl, fetch: event.fetch });
		const view = await getRepoByRepoDid(ctx, repoDid).catch((cause) =>
			httpStatusFor(cause) === 404 ? null : toHttpError(cause, "Could not load repository")
		);
		if (!view) error(404, `${repoDid} does not exist`);
		const response = knotRedirect(view, repoDid, service, event.url);
		trackClone(event, view, repoDid, service);
		return response;
	};