Graphical PDS migrator for AT Protocol

fix posthog

+4 -1
deno.json
···
"exclude": ["**/_fresh/*"],
"imports": {
"@atproto/api": "npm:@atproto/api@^0.15.6",
"@knotbin/posthog-fresh": "jsr:@knotbin/posthog-fresh@^0.1.3",
"fresh": "jsr:@fresh/core@^2.0.0-alpha.33",
"@fresh/plugin-tailwind": "jsr:@fresh/plugin-tailwind@^0.0.1-alpha.7",
"preact": "npm:preact@^10.26.6",
"@preact/signals": "npm:@preact/signals@^2.0.4",
"tailwindcss": "npm:tailwindcss@^3.4.3"
···
"lib": ["dom", "dom.asynciterable", "dom.iterable", "deno.ns"],
"jsx": "precompile",
"jsxImportSource": "preact",
-
"jsxPrecompileSkipElements": ["a", "img", "source", "body", "html", "head"]
},
"unstable": ["kv"]
}
···
"exclude": ["**/_fresh/*"],
"imports": {
"@atproto/api": "npm:@atproto/api@^0.15.6",
+
"@bigmoves/atproto-oauth-client": "jsr:@bigmoves/atproto-oauth-client@^0.2.0",
"@knotbin/posthog-fresh": "jsr:@knotbin/posthog-fresh@^0.1.3",
"fresh": "jsr:@fresh/core@^2.0.0-alpha.33",
"@fresh/plugin-tailwind": "jsr:@fresh/plugin-tailwind@^0.0.1-alpha.7",
+
"posthog-js": "npm:posthog-js@1.120.0",
"preact": "npm:preact@^10.26.6",
"@preact/signals": "npm:@preact/signals@^2.0.4",
"tailwindcss": "npm:tailwindcss@^3.4.3"
···
"lib": ["dom", "dom.asynciterable", "dom.iterable", "deno.ns"],
"jsx": "precompile",
"jsxImportSource": "preact",
+
"jsxPrecompileSkipElements": ["a", "img", "source", "body", "html", "head"],
+
"types": ["node"]
},
"unstable": ["kv"]
}
+53
deno.lock
···
"version": "5",
"specifiers": {
"jsr:@bigmoves/atproto-oauth-client@*": "0.1.0",
"jsr:@fresh/core@^2.0.0-alpha.1": "2.0.0-alpha.33",
"jsr:@fresh/core@^2.0.0-alpha.33": "2.0.0-alpha.33",
"jsr:@fresh/plugin-tailwind@^0.0.1-alpha.7": "0.0.1-alpha.7",
···
"npm:jose@5.9.6": "5.9.6",
"npm:lucide-preact@*": "0.511.0_preact@10.26.6",
"npm:postcss@8.4.35": "8.4.35",
"npm:preact-feather@*": "4.2.1_preact@10.26.6",
"npm:preact-render-to-string@^6.5.11": "6.5.13_preact@10.26.6",
"npm:preact@^10.25.1": "10.26.6",
···
"npm:jose"
]
},
"@fresh/core@2.0.0-alpha.33": {
"integrity": "0263ad090120cca6f814bb5914383c74f67d494e552ed33cbf58d667f12d7e9f",
"dependencies": [
···
"cookie@0.7.2": {
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="
},
"cross-spawn@7.0.6": {
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dependencies": [
···
"reusify"
]
},
"fill-range@7.1.1": {
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dependencies": [
···
"source-map-js"
]
},
"preact-feather@4.2.1_preact@10.26.6": {
"integrity": "sha512-yK5kYW64AoOkm+xTtUjwcFx0zNrqVTbwmtww8G2AmAB6f8wyQgwZgc6oRXllSYeg7q1I8VbkUpErJuKJ6Vq2eA==",
"dependencies": [
···
"util-deprecate@1.0.2": {
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"which@2.0.2": {
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dependencies": [
···
"integrity": "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg=="
}
},
"workspace": {
"dependencies": [
"jsr:@fresh/core@^2.0.0-alpha.33",
"jsr:@fresh/plugin-tailwind@^0.0.1-alpha.7",
"jsr:@knotbin/posthog-fresh@~0.1.3",
"npm:@atproto/api@~0.15.6",
"npm:@preact/signals@^2.0.4",
"npm:preact@^10.26.6",
"npm:tailwindcss@^3.4.3"
]
···
"version": "5",
"specifiers": {
"jsr:@bigmoves/atproto-oauth-client@*": "0.1.0",
+
"jsr:@bigmoves/atproto-oauth-client@0.2": "0.2.0",
"jsr:@fresh/core@^2.0.0-alpha.1": "2.0.0-alpha.33",
"jsr:@fresh/core@^2.0.0-alpha.33": "2.0.0-alpha.33",
"jsr:@fresh/plugin-tailwind@^0.0.1-alpha.7": "0.0.1-alpha.7",
···
"npm:jose@5.9.6": "5.9.6",
"npm:lucide-preact@*": "0.511.0_preact@10.26.6",
"npm:postcss@8.4.35": "8.4.35",
+
"npm:posthog-js@*": "1.120.0",
+
"npm:posthog-js@1.120.0": "1.120.0",
+
"npm:posthog-js@^1.120.0": "1.247.0",
"npm:preact-feather@*": "4.2.1_preact@10.26.6",
"npm:preact-render-to-string@^6.5.11": "6.5.13_preact@10.26.6",
"npm:preact@^10.25.1": "10.26.6",
···
"npm:jose"
]
},
+
"@bigmoves/atproto-oauth-client@0.2.0": {
+
"integrity": "5c3ca124dd52eff51dace83790779ebe48c4b41559b799e16c8750bd415f2124",
+
"dependencies": [
+
"npm:@atproto-labs/handle-resolver-node",
+
"npm:@atproto-labs/simple-store",
+
"npm:@atproto/jwk",
+
"npm:@atproto/oauth-client",
+
"npm:@atproto/oauth-types",
+
"npm:jose"
+
]
+
},
"@fresh/core@2.0.0-alpha.33": {
"integrity": "0263ad090120cca6f814bb5914383c74f67d494e552ed33cbf58d667f12d7e9f",
"dependencies": [
···
"cookie@0.7.2": {
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="
},
+
"core-js@3.42.0": {
+
"integrity": "sha512-Sz4PP4ZA+Rq4II21qkNqOEDTDrCvcANId3xpIgB34NDkWc3UduWj2dqEtN9yZIq8Dk3HyPI33x9sqqU5C8sr0g==",
+
"scripts": true
+
},
"cross-spawn@7.0.6": {
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dependencies": [
···
"reusify"
]
},
+
"fflate@0.4.8": {
+
"integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA=="
+
},
"fill-range@7.1.1": {
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dependencies": [
···
"source-map-js"
]
},
+
"posthog-js@1.120.0": {
+
"integrity": "sha512-A1FOJabDjt0mFg2ePfgqeZPUQl8WY6L+fNNfN/89gqvPoI7HmHta2hY/9tDQ+mBRHjj4nah5KK/EWqJHq69fGw==",
+
"dependencies": [
+
"fflate",
+
"preact@10.26.7"
+
],
+
"deprecated": true
+
},
+
"posthog-js@1.247.0": {
+
"integrity": "sha512-ml7QRfNbXhEHTS/g0HML2Vfs/goZELyFoKlmraJZ9nwyoQq9l4Q/8TLV/JLIlr7/6Io/sSDAwM6xBbJ/8pVBzQ==",
+
"dependencies": [
+
"core-js",
+
"fflate",
+
"preact@10.26.7",
+
"web-vitals"
+
]
+
},
"preact-feather@4.2.1_preact@10.26.6": {
"integrity": "sha512-yK5kYW64AoOkm+xTtUjwcFx0zNrqVTbwmtww8G2AmAB6f8wyQgwZgc6oRXllSYeg7q1I8VbkUpErJuKJ6Vq2eA==",
"dependencies": [
···
"util-deprecate@1.0.2": {
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
+
"web-vitals@4.2.4": {
+
"integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw=="
+
},
"which@2.0.2": {
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dependencies": [
···
"integrity": "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg=="
}
},
+
"redirects": {
+
"https://esm.sh/posthog-js": "https://esm.sh/posthog-js@1.247.0"
+
},
+
"remote": {
+
"https://esm.sh/posthog-js@1.120.0": "e7a2cb35b74bda7ed8ba7e0ee7ce53a11ebba5c3ebf9e595b0ea0168a4c20202",
+
"https://esm.sh/posthog-js@1.120.0/denonext/posthog-js.mjs": "9ca5a3b08bc4188793e010ff059fc81ad48327d0ac618d7768bdb44f4e938492",
+
"https://esm.sh/posthog-js@1.247.0": "cca238e1a47ea00f3d01a6e4bc0cf1985096ee6e0c09553e4e72ce0a0abd9955",
+
"https://esm.sh/posthog-js@1.247.0/denonext/posthog-js.mjs": "6dbdabdcce8c35240b34086133030bd3cdd8a3998fe5c42b03b57180ceb891e4"
+
},
"workspace": {
"dependencies": [
+
"jsr:@bigmoves/atproto-oauth-client@0.2",
"jsr:@fresh/core@^2.0.0-alpha.33",
"jsr:@fresh/plugin-tailwind@^0.0.1-alpha.7",
"jsr:@knotbin/posthog-fresh@~0.1.3",
"npm:@atproto/api@~0.15.6",
"npm:@preact/signals@^2.0.4",
+
"npm:posthog-js@1.120.0",
"npm:preact@^10.26.6",
"npm:tailwindcss@^3.4.3"
]
+114
islands/PostHogAnalytics.tsx
···
···
+
import { useEffect } from "preact/hooks";
+
+
interface PostHogConfig {
+
api_host: string;
+
[key: string]: unknown; // Allow additional optional properties
+
}
+
+
interface PostHogInstance {
+
__SV?: number;
+
_i: Array<[string, PostHogConfig, string?]>;
+
people?: PostHogPeople;
+
init: (apiKey: string, config: PostHogConfig, name?: string) => void;
+
capture: (event: string, properties?: Record<string, unknown>) => void;
+
identify: (distinctId: string, properties?: Record<string, unknown>) => void;
+
toString: (includeStub?: number) => string;
+
[key: string]: unknown;
+
}
+
+
interface PostHogPeople {
+
toString: () => string;
+
[key: string]: unknown;
+
}
+
+
declare global {
+
var posthog: PostHogInstance | undefined;
+
}
+
+
interface PostHogProps {
+
apiKey: string;
+
apiHost?: string;
+
}
+
+
export default function PostHogAnalytics({
+
apiKey,
+
apiHost = "https://us.i.posthog.com"
+
}: PostHogProps) {
+
useEffect(() => {
+
// PostHog initialization script
+
(function(t: Document, e: PostHogInstance) {
+
let o: string[];
+
let n: number;
+
let p: HTMLScriptElement;
+
let r: HTMLScriptElement | null;
+
+
if (e.__SV) return;
+
+
globalThis.posthog = e;
+
e._i = [];
+
e.init = function(i: string, s: PostHogConfig, a?: string) {
+
function g(target: Record<string, unknown>, methodName: string) {
+
const parts = methodName.split(".");
+
if (2 == parts.length) {
+
target = target[parts[0]] as Record<string, unknown>;
+
methodName = parts[1];
+
}
+
target[methodName] = function() {
+
(target as { push: (args: unknown[]) => void }).push([methodName].concat(Array.prototype.slice.call(arguments, 0)));
+
};
+
}
+
+
p = t.createElement("script");
+
p.type = "text/javascript";
+
p.crossOrigin = "anonymous";
+
p.async = true;
+
p.src = s.api_host.replace(".i.posthog.com", "-assets.i.posthog.com") + "/static/array.js";
+
r = t.getElementsByTagName("script")[0];
+
if (r && r.parentNode) {
+
r.parentNode.insertBefore(p, r);
+
}
+
+
let u: PostHogInstance = e;
+
if (typeof a !== "undefined") {
+
u = (e as Record<string, PostHogInstance>)[a] = {} as PostHogInstance;
+
u._i = [];
+
} else {
+
a = "posthog";
+
}
+
+
u.people = u.people || {} as PostHogPeople;
+
u.toString = function(includeStub?: number) {
+
let name = "posthog";
+
if ("posthog" !== a) {
+
name += "." + a;
+
}
+
if (!includeStub) {
+
name += " (stub)";
+
}
+
return name;
+
};
+
+
u.people.toString = function() {
+
return u.toString(1) + ".people (stub)";
+
};
+
+
o = "init capture register register_once register_for_session unregister unregister_for_session getFeatureFlag getFeatureFlagPayload isFeatureEnabled reloadFeatureFlags updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures on onFeatureFlags onSessionId getSurveys getActiveMatchingSurveys renderSurvey canRenderSurvey getNextSurveyStep identify setPersonProperties group resetGroups setPersonPropertiesForFlags resetPersonPropertiesForFlags setGroupPropertiesForFlags resetGroupPropertiesForFlags reset get_distinct_id getGroups get_session_id get_session_replay_url alias set_config startSessionRecording stopSessionRecording sessionRecordingStarted captureException loadToolbar get_property getSessionProperty createPersonProfile opt_in_capturing opt_out_capturing has_opted_in_capturing has_opted_out_capturing clear_opt_in_out_capturing debug".split(" ");
+
+
for (n = 0; n < o.length; n++) {
+
g(u as Record<string, unknown>, o[n]);
+
}
+
+
e._i.push([i, s, a]);
+
};
+
+
e.__SV = 1;
+
})(document, globalThis.posthog || {} as PostHogInstance);
+
+
// Initialize PostHog
+
if (globalThis.posthog) {
+
globalThis.posthog.init(apiKey, { api_host: apiHost });
+
}
+
}, [apiKey, apiHost]);
+
+
return null; // This component doesn't render anything visible
+
}
+15
islands/PostHogInitializer.tsx
···
···
+
import { useEffect } from "preact/hooks";
+
import posthog from "posthog-js";
+
+
interface Props {
+
apiKey: string;
+
apiHost: string;
+
}
+
+
export default function PostHogInitializer({ apiKey, apiHost }: Props) {
+
useEffect(() => {
+
posthog.default.init(apiKey, { api_host: apiHost });
+
}, [apiKey, apiHost]);
+
+
return null;
+
}
+23 -1
islands/SocialLinks.tsx
···
const [starCount, setStarCount] = useState<number | null>(null);
useEffect(() => {
const fetchRepoInfo = async () => {
try {
const response = await fetch("https://api.github.com/repos/knotbin/airport");
const data: GitHubRepo = await response.json();
setStarCount(data.stargazers_count);
} catch (error) {
console.error("Failed to fetch GitHub repo info:", error);
}
};
-
fetchRepoInfo();
}, []);
const formatStarCount = (count: number | null) => {
···
const [starCount, setStarCount] = useState<number | null>(null);
useEffect(() => {
+
const CACHE_KEY = 'github_stars';
+
const CACHE_DURATION = 15 * 60 * 1000; // 15 minutes in milliseconds
+
const fetchRepoInfo = async () => {
try {
const response = await fetch("https://api.github.com/repos/knotbin/airport");
const data: GitHubRepo = await response.json();
+
const cacheData = {
+
count: data.stargazers_count,
+
timestamp: Date.now()
+
};
+
localStorage.setItem(CACHE_KEY, JSON.stringify(cacheData));
setStarCount(data.stargazers_count);
} catch (error) {
console.error("Failed to fetch GitHub repo info:", error);
}
};
+
const getCachedStars = () => {
+
const cached = localStorage.getItem(CACHE_KEY);
+
if (cached) {
+
const { count, timestamp } = JSON.parse(cached);
+
if (Date.now() - timestamp < CACHE_DURATION) {
+
setStarCount(count);
+
return true;
+
}
+
}
+
return false;
+
};
+
+
if (!getCachedStars()) {
+
fetchRepoInfo();
+
}
}, []);
const formatStarCount = (count: number | null) => {
+1 -1
lib/oauth/client.ts
···
-
import { AtprotoOAuthClient } from "jsr:@bigmoves/atproto-oauth-client";
import { SessionStore, StateStore } from "../storage.ts";
export const createClient = (db: Deno.Kv) => {
···
+
import { AtprotoOAuthClient } from "@bigmoves/atproto-oauth-client";
import { SessionStore, StateStore } from "../storage.ts";
export const createClient = (db: Deno.Kv) => {
+5 -5
routes/_app.tsx
···
import { type PageProps } from "fresh";
import Header from "../islands/Header.tsx";
-
import { PostHogAnalytics } from "@knotbin/posthog-fresh";
export default function App({ Component }: PageProps) {
return (
<html>
<head>
···
</head>
<script defer src="https://cloud.umami.is/script.js" data-website-id={Deno.env.get("UMAMI_ID")}></script>
<body>
-
<PostHogAnalytics
-
apiKey={Deno.env.get("PUBLIC_POSTHOG_KEY")!}
-
apiHost={Deno.env.get("PUBLIC_POSTHOG_HOST")!}
-
/>
<Header />
<main className="pt-8">
<Component />
···
import { type PageProps } from "fresh";
import Header from "../islands/Header.tsx";
+
import PostHogInitializer from "../islands/PostHogInitializer.tsx";
export default function App({ Component }: PageProps) {
+
const apiKey = Deno.env.get("PUBLIC_POSTHOG_KEY")!;
+
const apiHost = Deno.env.get("PUBLIC_POSTHOG_HOST")!;
+
return (
<html>
<head>
···
</head>
<script defer src="https://cloud.umami.is/script.js" data-website-id={Deno.env.get("UMAMI_ID")}></script>
<body>
+
<PostHogInitializer apiKey={apiKey} apiHost={apiHost} />
<Header />
<main className="pt-8">
<Component />
+5
routes/_error.tsx
···
import { PageProps, HttpError } from "fresh";
export default function ErrorPage(props: PageProps) {
const error = props.error; // Contains the thrown Error or HTTPError
if (error instanceof HttpError) {
const status = error.status; // HTTP status code
// Render a 404 not found page
if (status === 404) {
···
import { PageProps, HttpError } from "fresh";
+
import posthog from "posthog-js";
export default function ErrorPage(props: PageProps) {
const error = props.error; // Contains the thrown Error or HTTPError
if (error instanceof HttpError) {
+
posthog.default.capture('error', {
+
error: error.message,
+
status: error.status,
+
});
const status = error.status; // HTTP status code
// Render a 404 not found page
if (status === 404) {