this repo has no description
0
fork

Configure Feed

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

Add Firefox Support & Privacy Consent

+217 -55
+5
CHANGELOG.md
··· 7 7 8 8 ## [Unreleased] 9 9 10 + ## [1.2.0] - 2023-05-03 11 + 12 + - Adds support for Firefox 13 + - Adds privacy consent dialog for Google DNS (required by Mozilla) 14 + 10 15 ## [1.1.0] - 2023-05-03 11 16 12 17 - use DID for profile url instead of domain name
+4
README.md
··· 17 17 As of April 30, 2023, Bluesky is still invite-only and the web app is at https://staging.bsky.app. 18 18 19 19 You can find me there at [@adhdjesse.com](https://staging.bsky.app/profile/adhdjesse.com) 20 + 21 + **Contributors:** 22 + 23 + [@danielhuckmann.com](https://staging.bsky.app/profile/danielhuckmann.com) - Firefox Support & Privacy Consent
+47 -19
background.js
··· 1 - const tabsWithDID = new Map() 1 + // Set up cross-browser compatibility 2 + const runtime = typeof browser !== 'undefined' ? browser.runtime : chrome.runtime; 3 + const tabs = typeof browser !== 'undefined' ? browser.tabs : chrome.tabs; 4 + const storage = typeof browser !== 'undefined' ? browser.storage.local : chrome.storage.local; 5 + const browserAction = typeof browser !== 'undefined' ? browser.browserAction : chrome.browserAction; 6 + 7 + // On extension installation, check if privacy consent was already accepted and show it if not 8 + runtime.onInstalled.addListener(() => { 9 + storage.get("privacyConsentAccepted", ({ privacyConsentAccepted }) => { 10 + if (typeof privacyConsentAccepted === "undefined" || !privacyConsentAccepted) { 11 + tabs.create({ url: "privacy_consent.html" }); 12 + } 13 + }); 14 + }); 15 + 16 + // If the message 'SHOW_CONSENT' is received, open the privacy consent tab 17 + runtime.onMessage.addListener((message, sender, sendResponse) => { 18 + if (message.type === 'SHOW_CONSENT') { 19 + tabs.create({ url: "privacy_consent.html" }); 20 + } 21 + }); 2 22 3 - const bskyAppUrl = "https://staging.bsky.app" 23 + // Map to store tabs with DIDs 24 + const tabsWithDID = new Map(); 4 25 26 + // URL of the Bluesky Web Applications 27 + const bskyAppUrl = 'https://staging.bsky.app'; 28 + 29 + // Function to set the extension icon 5 30 function setIcon(tabId, iconName) { 6 - chrome.action.setIcon({ path: iconName, tabId }) 31 + browserAction.setIcon({ path: iconName, tabId }); 7 32 } 8 33 9 - chrome.runtime.onInstalled.addListener(() => { 10 - chrome.tabs.query({}, (tabs) => { 11 - tabs.forEach((tab) => setIcon(tab.id, "logo48_gray.png")) 12 - }) 13 - }) 34 + // On extension installation, set the icon to gray for all tabs 35 + runtime.onInstalled.addListener(() => { 36 + tabs.query({}, (tabs) => { 37 + tabs.forEach((tab) => setIcon(tab.id, 'logo48_gray.png')); 38 + }); 39 + }); 14 40 15 - chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { 41 + // When a message is received from the DNS check, set the icon color to blue. 42 + runtime.onMessage.addListener((message, sender, sendResponse) => { 16 43 if (message.type === "DID_FOUND") { 17 - setIcon(sender.tab.id, "logo48.png") 18 - tabsWithDID.set(sender.tab.id, message.did) 44 + setIcon(sender.tab.id, "logo48.png"); 45 + tabsWithDID.set(sender.tab.id, message.did); 19 46 } else { 20 - setIcon(sender.tab.id, "logo48_gray.png") 21 - tabsWithDID.delete(sender.tab.id) 47 + setIcon(sender.tab.id, "logo48_gray.png"); 48 + tabsWithDID.delete(sender.tab.id); 22 49 } 23 - }) 50 + }); 24 51 25 - chrome.action.onClicked.addListener((tab) => { 26 - const did = tabsWithDID.get(tab.id) 52 + // When the extension icon is clicked, open the profile page if there's a DID 53 + browserAction.onClicked.addListener((tab) => { 54 + const did = tabsWithDID.get(tab.id); 27 55 if (did) { 28 - const newUrl = `${bskyAppUrl}/profile/${did}` 29 - chrome.tabs.create({ url: newUrl }) 56 + const newUrl = `${bskyAppUrl}/profile/${did}`; 57 + tabs.create({ url: newUrl }); 30 58 } 31 - }) 59 + });
+50 -36
content.js
··· 1 - function getDomainName() { 2 - const hostname = window.location.hostname 3 - return hostname.replace(/^www\./, "") 4 - } 1 + // Set up cross-browser compatibility 2 + const runtime = typeof browser !== 'undefined' ? browser.runtime : chrome.runtime; 3 + const tabs = typeof browser !== 'undefined' ? browser.tabs : chrome.tabs; 4 + const storage = typeof browser !== 'undefined' ? browser.storage.local : chrome.storage.local; 5 5 6 - async function checkForDID(domain) { 7 - // We use Google's DNS over HTTPS API to resolve the TXT record 8 - const response = await fetch( 9 - `https://dns.google/resolve?name=_atproto.${domain}&type=TXT` 10 - ) 11 - const data = await response.json() 6 + // Function to perform actions based on the user's privacy consent 7 + function performAction(privacyConsentAccepted) { 8 + // If the user has accepted the privacy consent 9 + if (privacyConsentAccepted) { 10 + // Function to get the domain name from the current hostname 11 + function getDomainName() { 12 + const hostname = window.location.hostname; 13 + return hostname.replace(/^www\./, ''); 14 + } 12 15 13 - // We use the TXT record type to avoid CORS issues 14 - const records = data?.Answer?.filter((record) => record.type === 16) || [] 16 + // Function to check for a DID in the domain's TXT records 17 + async function checkForDID(domain) { 18 + const response = await fetch( 19 + `https://dns.google/resolve?name=_atproto.${domain}&type=TXT` 20 + ); 21 + const data = await response.json(); 15 22 16 - // We filter out all records that are not TXT records 17 - const didRecord = records.find((record) => 18 - record.data.includes("did=did:plc:") 19 - ) 23 + const records = data?.Answer?.filter((record) => record.type === 16) || []; 20 24 21 - // We return the DID if we found one 22 - return didRecord ? didRecord.data.replace("did=", "") : null 23 - } 25 + const didRecord = records.find((record) => 26 + record.data.includes("did=did:plc:") 27 + ); 24 28 25 - // We check for a DID on the current domain 26 - ;(async function () { 27 - const domain = getDomainName() 28 - const did = await checkForDID(domain) 29 + return didRecord ? didRecord.data.replace("did=", "") : null; 30 + } 31 + 32 + // Immediately invoked function to check for a DID and send a message based on the result 33 + (async function () { 34 + const domain = getDomainName(); 35 + const did = await checkForDID(domain); 29 36 30 - if (did) { 31 - chrome.runtime.sendMessage({ type: "DID_FOUND", did }) 37 + if (did) { 38 + runtime.sendMessage({ type: "DID_FOUND", did }); 39 + } else { 40 + runtime.sendMessage({ type: "DID_NOT_FOUND" }); 41 + } 42 + })(); 43 + 44 + // Listener for the 'GET_DOMAIN' message and respond with the domain 45 + runtime.onMessage.addListener((message, sender, sendResponse) => { 46 + if (message.type === 'GET_DOMAIN') { 47 + sendResponse({ domain: getDomainName() }); 48 + } 49 + }); 32 50 } else { 33 - chrome.runtime.sendMessage({ type: "DID_NOT_FOUND" }) 51 + // If the user hasn't accepted the privacy consent, show the consent page again 52 + tabs.create({ url: "privacy_consent.html" }); 34 53 } 35 - })() 54 + } 36 55 37 - // We listen for messages from the background script 38 - chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { 39 - if (message.type === "GET_DID") { 40 - checkForDID(getDomainName()) 41 - .then((did) => sendResponse({ did })) 42 - .catch(() => sendResponse({ did: null })) 43 - return true // Indicate that the response will be sent asynchronously. 44 - } 45 - }) 56 + // Get the user's privacy consent from the storage and perform actions accordingly 57 + storage.get("privacyConsentAccepted", ({ privacyConsentAccepted }) => { 58 + performAction(privacyConsentAccepted); 59 + });
+98
privacy_consent.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8"> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 + <title>Skylink Privacy Consent</title> 7 + <style> 8 + html { 9 + line-height: 1.15; 10 + -webkit-text-size-adjust: 100%; 11 + } 12 + 13 + body { 14 + margin: 0; 15 + } 16 + 17 + h1 { 18 + font-size: 2em; 19 + margin: 0.67em 0; 20 + } 21 + 22 + a { 23 + background-color: transparent; 24 + } 25 + 26 + p { 27 + margin: 1em 0; 28 + } 29 + 30 + ul { 31 + margin: 1em 0; 32 + padding: 0 0 0 40px; 33 + } 34 + 35 + li { 36 + list-style-type: disc; 37 + margin: 0 0 0.25em 0; 38 + } 39 + 40 + button { 41 + font-family: inherit; 42 + font-size: 100%; 43 + line-height: 1.15; 44 + margin: 0; 45 + overflow: visible; 46 + text-transform: none; 47 + } 48 + 49 + button::-moz-focus-inner { 50 + border-style: none; 51 + padding: 0; 52 + } 53 + 54 + button:-moz-focusring { 55 + outline: 1px dotted ButtonText; 56 + } 57 + .centered-container { 58 + position: absolute; 59 + top: 50%; 60 + left: 50%; 61 + transform: translate(-50%, -50%); 62 + } 63 + </style> 64 + </head> 65 + <body> 66 + <div class="centered-container"> 67 + <h1>Privacy Consent for SkyLink - Bluesky DID Detector</h1> 68 + <p>While <b>no information is collected by the extension author</b>, this extension makes use of the <a href="https://developers.google.com/speed/public-dns/">Google DNS service</a> for required functionality.</p> 69 + <p>The Google DNS service is used because browser extensions do not have native access to DNS, which is needed to lookup the AT PROTO TXT record.</p> 70 + <p>A summary of what is collected by Google DNS: 71 + <ul> 72 + <li>the IP address of your device sending the DNS query</li> 73 + <li>Requested domain name</li> 74 + <li>Request type (TXT)</li> 75 + <li>Request Size</li> 76 + <li>Transport protocol (HTTPS)</li> 77 + <li>Client's autonomous system number</li> 78 + <li>User's geolocation: country, region, and city (no more specific than 1 km² and 1000 users)</li> 79 + <li>DNS Response code</li> 80 + <li>Google DNS server information</li> 81 + <li>Timestamp</li> 82 + <li>Processing time</li> 83 + <li>Response DNS flags (including AD, CD, DO, RD, and TC)</li> 84 + <li>Response size</li> 85 + <li>EDNS version</li> 86 + <li>EDNS option</li> 87 + <li>EDNS Client Subnet (ECS) (IP protocol and prefix length -- excluding the client IP address)</li> 88 + <li>Version string corresponding to HTTP path (/resolve)</li> 89 + <li>Response HTTP encoding, such as application/dns-message or json</li> 90 + </ul> 91 + </p> 92 + <p><a href="https://developers.google.com/speed/public-dns/privacy" target="_blank">Please Read and accept the full Google DNS privacy policy before using this addon.</a></p> 93 + <button id="accept">Accept (Continue)</button> 94 + <button id="decline">Decline (Remove Extension)</button> 95 + <script src="privacy_consent.js"></script> 96 + </div> 97 + </body> 98 + </html>
+13
privacy_consent.js
··· 1 + // Set up cross-browser compatibility 2 + const runtime = typeof browser !== 'undefined' ? browser.runtime : chrome.runtime; 3 + const storage = typeof browser !== 'undefined' ? browser.storage.local : chrome.storage.local; 4 + const management = typeof browser !== 'undefined' ? browser.management : chrome.management; 5 + 6 + document.getElementById("accept").addEventListener("click", function () { 7 + storage.set({ privacyConsentAccepted: true }); 8 + window.close(); 9 + }); 10 + 11 + document.getElementById("decline").addEventListener("click", function () { 12 + management.uninstallSelf({ showConfirmDialog: true }); 13 + });