this repo has no description
0
fork

Configure Feed

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

at 0cfafcd017d06cd7cbc504569eac057130db4e1e 131 lines 4.4 kB view raw
1// Set up cross-browser compatibility 2const runtime = 3 typeof browser !== "undefined" ? browser.runtime : chrome.runtime 4const storage = 5 typeof browser !== "undefined" ? browser.storage.local : chrome.storage.local 6 7// Make sure that we don't DoS the regex if someone supplies too large of a DID 8// 1024 was arbitratily chosen to limit performance impact, I couldn't find any specicied limits on DID length 9const MAX_DID_LENGTH = 1024 10 11// Regular expression to validate the DID format 12// https://w3c.github.io/did-core/#did-syntax 13const didRegex = 14 /^did:plc:([a-zA-Z0-9._-]+(:[a-zA-Z0-9._-]+)*|((%[0-9A-Fa-f]{2})|[a-zA-Z0-9._-])+(:((%[0-9A-Fa-f]{2})|[a-zA-Z0-9._-])+)*$)/ 15 16// Function to validate the DID string 17function isValidDID(didString) { 18 return didString.length <= MAX_DID_LENGTH && didRegex.test(didString) 19} 20 21// Function to get the domain name from the current hostname 22function getDomainName() { 23 const hostname = window.location.hostname 24 return hostname.replace(/^www\./, "") 25} 26 27// Function to validate the domain name 28function isValidDomain(domain) { 29 const MAX_DOMAIN_LENGTH = 255 30 31 if (domain.length > MAX_DOMAIN_LENGTH) { 32 return false 33 } 34 35 try { 36 // Use the build in URL constructor to validate the URL, if doesn't throw an error, the domain is valid 37 // This is a better choice than a regex since it should properly support punycode/international domains 38 new URL(`https://${domain}`) 39 return true 40 } catch (error) { 41 // The URL constructor threw an error, so the domain is not valid 42 return false 43 } 44} 45 46// Function to check for a DID in the domain's TXT records 47async function checkForDIDDNS(domain) { 48 try { 49 const response = await fetch( 50 `https://dns.google/resolve?name=_atproto.${domain}&type=TXT` 51 ) 52 const data = await response.json() 53 54 // We use the TXT record type to avoid CORS issues 55 const records = data?.Answer?.filter((record) => record.type === 16) || [] 56 57 // We filter out all records that are not TXT records 58 const didRecord = records.find((record) => 59 record.data.includes("did=did:plc:") 60 ) 61 62 // We return the DID if we found one and it's valid 63 return didRecord && isValidDID(didRecord.data.replace("did=", "")) 64 ? didRecord.data.replace("did=", "") 65 : null 66 } catch (error) { 67 return null 68 } 69} 70 71// Function to check for a DID in the well-known (not .well-known) location 72async function checkForDIDHTTPS(domain) { 73 try { 74 const response = await fetch( 75 `https://${domain}/xrpc/com.atproto.identity.resolveHandle` 76 ) 77 78 if (!response.headers.get("Content-Type")?.includes("application/json")) { 79 throw new Error("Invalid Content-Type") 80 } 81 82 const data = await response.json() 83 return data.did && isValidDID(data.did) ? data.did : null 84 } catch (error) { 85 return null 86 } 87} 88 89// Main function to perform actions, but only if the privacy consent has been accepted 90function performAction(privacyConsentAccepted) { 91 // If the user has accepted the privacy consent 92 if (privacyConsentAccepted) { 93 // We check for a DID on the current domain 94 ;(async function () { 95 const domain = getDomainName() 96 if (isValidDomain(domain)) { 97 const domainDID = await checkForDIDDNS(domain) 98 const httpsDID = await checkForDIDHTTPS(domain) 99 100 if (domainDID) { 101 runtime.sendMessage({ type: "DID_FOUND", did: domainDID }) 102 } else if (httpsDID) { 103 runtime.sendMessage({ type: "DID_FOUND", did: httpsDID }) 104 } else { 105 runtime.sendMessage({ type: "DID_NOT_FOUND" }) 106 } 107 } 108 })() 109 110 // We listen for messages from the background script 111 runtime.onMessage.addListener((message, sender, sendResponse) => { 112 if (message.type === "GET_DID") { 113 const domain = getDomainName() 114 if (isValidDomain(domain)) { 115 checkForDIDDNS(domain) 116 .then((did) => sendResponse({ did })) 117 .catch(() => sendResponse({ did: null })) 118 return true // Indicate that the response will be sent asynchronously. 119 } 120 } 121 }) 122 } else { 123 // Do nothing since the consent form has not been accepted. 124 return 125 } 126} 127 128// Get the user's privacy consent from the storage and perform actions accordingly 129storage.get("privacyConsentAccepted", ({ privacyConsentAccepted }) => { 130 performAction(privacyConsentAccepted) 131})