view who was fronting when a record was made

fix: make replies have fronter after being created

ptr.pet a88e7061 2170a51c

verified
Changed files
+73 -18
src
+38 -5
src/entrypoints/background.ts
··· 9 memberUriString, 10 putFronter, 11 frontersCache, 12 } from "@/lib/utils"; 13 - import { parseResourceUri, ResourceUri } from "@atcute/lexicons"; 14 15 export default defineBackground({ 16 persistent: true, ··· 82 console.error(`fronter write: ${resp.error}`); 83 } 84 } 85 // hijack timeline fronter message because when a write is made it is either on the timeline 86 // or its a reply to a depth === 0 post on a threaded view, which is the same as a timeline post 87 browser.tabs.sendMessage(sender.tab?.id!, { ··· 142 ]), 143 ), 144 ); 145 browser.tabs.sendMessage(sender.tab?.id!, { 146 type: "TIMELINE_FRONTER", 147 results, ··· 149 // console.log("sent timeline fronters", results); 150 }; 151 const handleThread = async ( 152 - { data: { body } }: any, 153 sender: globalThis.Browser.runtime.MessageSender, 154 ) => { 155 const data: any = JSON.parse(body); 156 const promises = (data.thread as any[]).flatMap((item) => { 157 return frontersCache.get(item.uri).then(async (cachedFronter) => { ··· 165 } 166 return fronter.value; 167 }); 168 - return promise.then(async (fronter) => { 169 if (!fronter) return; 170 const parsedUri = await cacheFronter(item.uri, fronter); 171 if (item.depth === 0) await setTabFronter(item.uri, fronter); 172 return { 173 rkey: parsedUri.rkey!, ··· 188 ), 189 ), 190 ); 191 browser.tabs.sendMessage(sender.tab?.id!, { 192 - type: "THREAD_FRONTER", 193 results, 194 }); 195 // console.log("sent thread fronters", results); ··· 197 198 browser.runtime.onMessage.addListener(async (message, sender) => { 199 if (message.type !== "RESPONSE_CAPTURED") return; 200 - // console.log("handling response event", message); 201 switch (message.data.type as string) { 202 case "write": 203 await handleWrite(
··· 9 memberUriString, 10 putFronter, 11 frontersCache, 12 + parseSocialAppPostUrl, 13 } from "@/lib/utils"; 14 + import { 15 + parseCanonicalResourceUri, 16 + parseResourceUri, 17 + ResourceUri, 18 + } from "@atcute/lexicons"; 19 20 export default defineBackground({ 21 persistent: true, ··· 87 console.error(`fronter write: ${resp.error}`); 88 } 89 } 90 + if (results.length === 0) return; 91 // hijack timeline fronter message because when a write is made it is either on the timeline 92 // or its a reply to a depth === 0 post on a threaded view, which is the same as a timeline post 93 browser.tabs.sendMessage(sender.tab?.id!, { ··· 148 ]), 149 ), 150 ); 151 + if (results.size === 0) return; 152 browser.tabs.sendMessage(sender.tab?.id!, { 153 type: "TIMELINE_FRONTER", 154 results, ··· 156 // console.log("sent timeline fronters", results); 157 }; 158 const handleThread = async ( 159 + { 160 + data: { body, requestUrl, documentUrl }, 161 + }: { data: { body: string; requestUrl: string; documentUrl: string } }, 162 sender: globalThis.Browser.runtime.MessageSender, 163 ) => { 164 + // check if this request was made for fetching replies 165 + // if anchor is not the same as current document url, that is the case 166 + // which means the depth of the returned posts are invalid to us, in the case of THREAD_FRONTER 167 + // if so we will use TIMELINE_FRONTER to send it back to content script 168 + let isReplyThreadFetch = false; 169 + const parsedDocumentUri = parseSocialAppPostUrl(documentUrl); 170 + const anchorUri = new URL(requestUrl).searchParams.get("anchor"); 171 + // console.log( 172 + // "parsedDocumentUri", 173 + // parsedDocumentUri, 174 + // "anchorUri", 175 + // anchorUri, 176 + // ); 177 + if (parsedDocumentUri && anchorUri) { 178 + const parsedAnchorUri = expect(parseResourceUri(anchorUri)); 179 + isReplyThreadFetch = parsedDocumentUri.rkey !== parsedAnchorUri.rkey; 180 + } 181 + // console.log("isReplyThreadFetch", isReplyThreadFetch); 182 const data: any = JSON.parse(body); 183 const promises = (data.thread as any[]).flatMap((item) => { 184 return frontersCache.get(item.uri).then(async (cachedFronter) => { ··· 192 } 193 return fronter.value; 194 }); 195 + return promise.then(async (fronter): Promise<any> => { 196 if (!fronter) return; 197 const parsedUri = await cacheFronter(item.uri, fronter); 198 + if (isReplyThreadFetch) 199 + return { 200 + rkey: parsedUri.rkey!, 201 + ...fronter, 202 + }; 203 if (item.depth === 0) await setTabFronter(item.uri, fronter); 204 return { 205 rkey: parsedUri.rkey!, ··· 220 ), 221 ), 222 ); 223 + if (results.size === 0) return; 224 browser.tabs.sendMessage(sender.tab?.id!, { 225 + type: isReplyThreadFetch ? "TIMELINE_FRONTER" : "THREAD_FRONTER", 226 results, 227 }); 228 // console.log("sent thread fronters", results); ··· 230 231 browser.runtime.onMessage.addListener(async (message, sender) => { 232 if (message.type !== "RESPONSE_CAPTURED") return; 233 + console.log("handling response", message.data); 234 switch (message.data.type as string) { 235 case "write": 236 await handleWrite(
+35 -13
src/entrypoints/content.ts
··· 55 } 56 return authHeader?.split(" ")[1] || null; 57 }; 58 59 let detail: any = undefined; 60 if (response.url.includes("/xrpc/com.atproto.repo.applyWrites")) { ··· 84 detail = { 85 type: "thread", 86 body, 87 }; 88 } else if (response.url.includes("/xrpc/app.bsky.feed.getPosts")) { 89 detail = { ··· 118 el.textContent += ` [f: ${s}]`; 119 el.setAttribute("data-fronter", s); 120 }; 121 - const applyFrontersToPage = (fronters: Map<string, any>) => { 122 // console.log("applyFrontersToPage", fronters); 123 const match = parseSocialAppPostUrl(document.URL); 124 - // console.log(match, fronters); 125 - for (const el of document.querySelectorAll("[data-fronter]")) { 126 - const previousFronter = el.getAttribute("data-fronter")!; 127 - // remove fronter text 128 - el.textContent = el.textContent.replace(` [f: ${previousFronter}]`, ""); 129 - el.removeAttribute("data-fronter"); 130 } 131 if (fronters.size === 0) return; 132 for (const el of document.getElementsByTagName("a")) { ··· 134 const fronter = fronters.get(path); 135 if (!fronter || fronter.members.length === 0) continue; 136 const isFocusedPost = fronter.depth === 0; 137 - if (isFocusedPost && match && match.rkey !== fronter.rkey) continue; 138 - if (isFocusedPost && el.ariaLabel !== fronter.displayName) continue; 139 const displayNameElement = isFocusedPost 140 ? (el.firstElementChild?.firstElementChild?.firstElementChild 141 ?.firstElementChild?.firstElementChild ?? null) 142 : (el.parentElement?.firstElementChild?.firstElementChild 143 ?.firstElementChild?.firstElementChild ?? null); 144 if (!displayNameElement) continue; 145 - // console.log(path, fronter, displayNameElement); 146 applyFronterName(displayNameElement, fronter.members); 147 } 148 }; ··· 162 ]); 163 }), 164 ); 165 - // console.log("applying cached fronters"); 166 - applyFrontersToPage(updated); 167 }; 168 // check if we are on profile so we can update fronters if the post tab is clicked on 169 const postTabElement = document.querySelector( ··· 182 window.addEventListener("message", (event) => { 183 if (!["TIMELINE_FRONTER", "THREAD_FRONTER"].includes(event.data.type)) 184 return; 185 - applyFrontersToPage(event.data.results as Map<string, any>); 186 }); 187 }, 188 });
··· 55 } 56 return authHeader?.split(" ")[1] || null; 57 }; 58 + const getRequestUrl = () => { 59 + let url: string | null = null; 60 + if (args[0] instanceof Request) { 61 + url = args[0].url; 62 + } else { 63 + url = args[0].toString(); 64 + } 65 + return decodeURI(url); 66 + }; 67 68 let detail: any = undefined; 69 if (response.url.includes("/xrpc/com.atproto.repo.applyWrites")) { ··· 93 detail = { 94 type: "thread", 95 body, 96 + requestUrl: getRequestUrl(), 97 + documentUrl: document.location.href, 98 }; 99 } else if (response.url.includes("/xrpc/app.bsky.feed.getPosts")) { 100 detail = { ··· 129 el.textContent += ` [f: ${s}]`; 130 el.setAttribute("data-fronter", s); 131 }; 132 + const applyFrontersToPage = ( 133 + fronters: Map<string, any>, 134 + pageChange: boolean, 135 + ) => { 136 // console.log("applyFrontersToPage", fronters); 137 const match = parseSocialAppPostUrl(document.URL); 138 + console.log("applyFrontersToPage", match, fronters); 139 + if (pageChange) { 140 + console.log( 141 + "page change so clearing all elements with data-fronter attribute", 142 + ); 143 + for (const el of document.querySelectorAll("[data-fronter]")) { 144 + const previousFronter = el.getAttribute("data-fronter")!; 145 + // remove fronter text 146 + el.textContent = el.textContent.replace( 147 + ` [f: ${previousFronter}]`, 148 + "", 149 + ); 150 + el.removeAttribute("data-fronter"); 151 + } 152 } 153 if (fronters.size === 0) return; 154 for (const el of document.getElementsByTagName("a")) { ··· 156 const fronter = fronters.get(path); 157 if (!fronter || fronter.members.length === 0) continue; 158 const isFocusedPost = fronter.depth === 0; 159 + if (isFocusedPost) if (match && match.rkey !== fronter.rkey) continue; 160 + if (isFocusedPost) if (el.ariaLabel !== fronter.displayName) continue; 161 const displayNameElement = isFocusedPost 162 ? (el.firstElementChild?.firstElementChild?.firstElementChild 163 ?.firstElementChild?.firstElementChild ?? null) 164 : (el.parentElement?.firstElementChild?.firstElementChild 165 ?.firstElementChild?.firstElementChild ?? null); 166 if (!displayNameElement) continue; 167 applyFronterName(displayNameElement, fronter.members); 168 } 169 }; ··· 183 ]); 184 }), 185 ); 186 + console.log("applying cached fronters", updated); 187 + applyFrontersToPage(updated, true); 188 }; 189 // check if we are on profile so we can update fronters if the post tab is clicked on 190 const postTabElement = document.querySelector( ··· 203 window.addEventListener("message", (event) => { 204 if (!["TIMELINE_FRONTER", "THREAD_FRONTER"].includes(event.data.type)) 205 return; 206 + console.log(`received ${event.data.type} fronters`, event.data.results); 207 + applyFrontersToPage(event.data.results, false); 208 }); 209 }, 210 });