this repo has no description
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})