(READ ONLY) Margin is an open annotation layer for the internet. Powered by the AT Protocol. margin.at
extension web atproto comments
98
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 114 lines 2.8 kB view raw
1export type AnalyticsEvents = { 2 login_initiated: { handle: string }; 3 login_success: { handle: string; pds?: string }; 4 signup_initiated: { provider: string }; 5 user_logged_out: Record<string, never>; 6 7 annotation_created: { 8 url: string; 9 has_quote: boolean; 10 tag_count: number; 11 has_labels: boolean; 12 source?: "web" | "extension"; 13 }; 14 highlight_created: { 15 url: string; 16 tag_count: number; 17 has_color: boolean; 18 has_labels: boolean; 19 source?: "web" | "extension"; 20 }; 21 bookmark_created: { 22 url: string; 23 tag_count: number; 24 source?: "web" | "extension"; 25 }; 26 reply_created: { parent_uri: string; root_uri: string }; 27 28 item_liked: { 29 action: "like" | "unlike"; 30 type: "annotation" | "highlight" | "bookmark"; 31 }; 32 item_deleted: { type: "annotation" | "highlight" | "bookmark" }; 33 item_shared: { 34 method: "copy" | "bluesky" | "twitter" | "mastodon" | "email"; 35 item_type?: string; 36 }; 37 item_added_to_collection: Record<string, never>; 38 39 collection_created: { name: string }; 40 collection_deleted: Record<string, never>; 41 42 extension_installed: { version: string; browser: string }; 43 extension_connected: { did: string }; 44 popup_opened: { authenticated: boolean }; 45 extension_tab_switched: { tab: string }; 46 47 highlights_imported: { total: number; completed: number; failed: number }; 48 49 search_performed: { query: string }; 50 51 api_key_created: Record<string, never>; 52 theme_changed: { theme: string }; 53}; 54 55function getPostHog() { 56 if (typeof window === "undefined") return null; 57 return window.posthog ?? null; 58} 59 60export const analytics = { 61 capture<E extends keyof AnalyticsEvents>( 62 event: E, 63 properties?: AnalyticsEvents[E], 64 ): void { 65 try { 66 getPostHog()?.capture( 67 event as string, 68 properties as Record<string, unknown>, 69 ); 70 } catch { 71 // ignore 72 } 73 }, 74 75 identify( 76 did: string, 77 properties: { handle: string; displayName?: string }, 78 ): void { 79 try { 80 getPostHog()?.identify(did, { 81 handle: properties.handle, 82 display_name: properties.displayName ?? undefined, 83 $set_once: { first_seen_at: new Date().toISOString() }, 84 }); 85 } catch { 86 // noop 87 } 88 }, 89 90 reset(): void { 91 try { 92 getPostHog()?.reset(); 93 } catch { 94 // noop 95 } 96 }, 97 98 captureException(error: unknown, properties?: Record<string, unknown>): void { 99 try { 100 const ph = getPostHog(); 101 if (!ph) return; 102 if (typeof ph.captureException === "function") { 103 ph.captureException(error, properties); 104 } else { 105 ph.capture("$exception", { 106 message: error instanceof Error ? error.message : String(error), 107 ...properties, 108 }); 109 } 110 } catch { 111 // noop 112 } 113 }, 114};