From 4a1d9fd74c2912a085e077ee25f275678fb96fe7 Mon Sep 17 00:00:00 2001 From: Andres Sosa Date: Mon, 23 Mar 2026 02:42:45 -0500 Subject: [PATCH] Add Chrome Extension (Manifest V3) for GitHub Chat Implements issue #1 - converts the web chat into a Chrome Extension that works on github.com/* pages. Features: - Manifest V3 compliant Chrome Extension - Floating toggle button (lower-right corner) to show/hide chat panel - iframe-based chat panel with per-repo chat rooms - Firebase Firestore real-time messaging (uses existing backend) - GitHub-themed dark UI matching the platform aesthetic - XSS protection via HTML escaping - Toolbar icon click also toggles the chat - Name prompt on first use, then persistent chat session - Delete own messages support - Responsive design optimized for the 320x480 panel size Closes #1 --- chrome-extension/background.js | 6 + chrome-extension/chat.html | 47 +++++ chrome-extension/chat.js | 183 +++++++++++++++++++ chrome-extension/content.css | 72 ++++++++ chrome-extension/content.js | 66 +++++++ chrome-extension/css/chat.css | 267 ++++++++++++++++++++++++++++ chrome-extension/css/normalize.css | 1 + chrome-extension/images/icon128.png | Bin 0 -> 1312 bytes chrome-extension/images/icon16.png | Bin 0 -> 247 bytes chrome-extension/images/icon48.png | Bin 0 -> 519 bytes chrome-extension/manifest.json | 34 ++++ 11 files changed, 676 insertions(+) create mode 100644 chrome-extension/background.js create mode 100644 chrome-extension/chat.html create mode 100644 chrome-extension/chat.js create mode 100644 chrome-extension/content.css create mode 100644 chrome-extension/content.js create mode 100644 chrome-extension/css/chat.css create mode 100644 chrome-extension/css/normalize.css create mode 100644 chrome-extension/images/icon128.png create mode 100644 chrome-extension/images/icon16.png create mode 100644 chrome-extension/images/icon48.png create mode 100644 chrome-extension/manifest.json diff --git a/chrome-extension/background.js b/chrome-extension/background.js new file mode 100644 index 0000000..2cdb88b --- /dev/null +++ b/chrome-extension/background.js @@ -0,0 +1,6 @@ +// Toggle chat panel when the extension icon is clicked +chrome.action.onClicked.addListener((tab) => { + if (tab.url && tab.url.startsWith('https://github.com/')) { + chrome.tabs.sendMessage(tab.id, { action: 'toggleChat' }); + } +}); diff --git a/chrome-extension/chat.html b/chrome-extension/chat.html new file mode 100644 index 0000000..d371743 --- /dev/null +++ b/chrome-extension/chat.html @@ -0,0 +1,47 @@ + + + + + + GitHub Chat + + + + +
+
+
+ + + +
+
+

GitHub Chat

+

Loading...

+
+
+ +
+
+

Welcome to GitHub Chat

+

Enter your name to start chatting

+ + +
+
+ + + + +
+ + + + + + diff --git a/chrome-extension/chat.js b/chrome-extension/chat.js new file mode 100644 index 0000000..bd85385 --- /dev/null +++ b/chrome-extension/chat.js @@ -0,0 +1,183 @@ +// GitHub Chat - Firebase Chat Logic +(function () { + 'use strict'; + + // Firebase configuration (from original repo) + var firebaseConfig = { + apiKey: "AIzaSyCIW4NPOTP6L2AZDJyZEkd9PkdNLLp8gbA", + authDomain: "inquid-chat.firebaseapp.com", + databaseURL: "https://inquid-chat-default-rtdb.firebaseio.com", + projectId: "inquid-chat", + storageBucket: "inquid-chat.appspot.com", + messagingSenderId: "771474336667", + appId: "1:771474336667:web:819d6a6fe187018f0a0f3e", + measurementId: "G-2SRNLJ8J2X" + }; + + firebase.initializeApp(firebaseConfig); + var db = firebase.firestore(); + + // Parse URL params for repo context + var params = new URLSearchParams(window.location.search); + var repoOwner = params.get('owner') || ''; + var repoName = params.get('repo') || ''; + var chatRoom = repoOwner && repoName ? repoOwner + '/' + repoName : 'general'; + + // UI elements + var roomNameEl = document.getElementById('chat-room-name'); + var roomContextEl = document.getElementById('chat-room-context'); + var namePrompt = document.getElementById('name-prompt'); + var usernameInput = document.getElementById('username-input'); + var usernameSubmit = document.getElementById('username-submit'); + var messagesContainer = document.querySelector('.messages'); + var messagesContent = document.getElementById('messages-content'); + var messageBox = document.querySelector('.message-box'); + var messageInput = document.getElementById('message'); + var sendBtn = document.getElementById('send-btn'); + + var myName = ''; + var unsubscribe = null; + + // Set room info in header + if (repoOwner && repoName) { + roomNameEl.textContent = repoName; + roomContextEl.textContent = repoOwner + '/' + repoName; + } else { + roomNameEl.textContent = 'GitHub Chat'; + roomContextEl.textContent = 'General Chat Room'; + } + + // Get Firestore collection for this chat room + function getCollection() { + return db.collection('rooms').doc(chatRoom.replace('/', '_')).collection('messages'); + } + + // Format timestamp + function formatTime(timestamp) { + if (!timestamp) return ''; + var d = timestamp.toDate ? timestamp.toDate() : new Date(timestamp); + var hours = d.getHours().toString().padStart(2, '0'); + var mins = d.getMinutes().toString().padStart(2, '0'); + return hours + ':' + mins; + } + + // Create a message element + function createMessageEl(data, docId) { + var div = document.createElement('div'); + var isPersonal = data.sender === myName; + div.className = 'message' + (isPersonal ? ' message-personal' : '') + ' new'; + div.id = 'message-' + docId; + + var content = ''; + if (!isPersonal) { + content += '' + escapeHtml(data.sender) + ''; + } + content += '' + escapeHtml(data.message) + ''; + content += '' + formatTime(data.timestamp) + ''; + + if (isPersonal) { + content += ''; + } + + div.innerHTML = content; + + // Delete handler + var deleteBtn = div.querySelector('.btn-delete'); + if (deleteBtn) { + deleteBtn.addEventListener('click', function () { + var id = this.getAttribute('data-id'); + getCollection().doc(id).delete().then(function () { + var el = document.getElementById('message-' + id); + if (el) el.remove(); + }); + }); + } + + return div; + } + + // Escape HTML to prevent XSS + function escapeHtml(text) { + var div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; + } + + // Scroll to bottom + function scrollToBottom() { + messagesContent.scrollTop = messagesContent.scrollHeight; + } + + // Start listening to messages + function startListening() { + if (unsubscribe) unsubscribe(); + + messagesContent.innerHTML = ''; + + unsubscribe = getCollection() + .orderBy('timestamp', 'asc') + .onSnapshot(function (snapshot) { + snapshot.docChanges().forEach(function (change) { + if (change.type === 'added') { + var el = createMessageEl(change.doc.data(), change.doc.id); + messagesContent.appendChild(el); + } else if (change.type === 'removed') { + var removed = document.getElementById('message-' + change.doc.id); + if (removed) removed.remove(); + } + }); + scrollToBottom(); + }); + } + + // Send a message + function sendMessage() { + var text = messageInput.value.trim(); + if (!text) return; + + // Clear input immediately for better UX + messageInput.value = ''; + messageInput.focus(); + + getCollection().add({ + message: text, + sender: myName, + timestamp: firebase.firestore.FieldValue.serverTimestamp(), + room: chatRoom + }).catch(function (err) { + // Restore text if send fails + messageInput.value = text; + console.error('Failed to send message:', err); + }); + } + + // Join chat with username + function joinChat() { + var name = usernameInput.value.trim(); + if (!name) return; + + myName = name; + namePrompt.style.display = 'none'; + messagesContainer.style.display = ''; + messageBox.style.display = ''; + messageInput.focus(); + startListening(); + } + + // Event listeners + usernameSubmit.addEventListener('click', joinChat); + usernameInput.addEventListener('keydown', function (e) { + if (e.key === 'Enter') { + e.preventDefault(); + joinChat(); + } + }); + + sendBtn.addEventListener('click', sendMessage); + messageInput.addEventListener('keydown', function (e) { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + sendMessage(); + } + }); +})(); diff --git a/chrome-extension/content.css b/chrome-extension/content.css new file mode 100644 index 0000000..ed165cc --- /dev/null +++ b/chrome-extension/content.css @@ -0,0 +1,72 @@ +/* GitHub Chat - Toggle Button & Panel */ +#github-chat-toggle { + position: fixed; + bottom: 24px; + right: 24px; + width: 56px; + height: 56px; + border-radius: 50%; + background: linear-gradient(135deg, #044f48, #2a7561); + border: none; + cursor: pointer; + z-index: 2147483646; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3); + display: flex; + align-items: center; + justify-content: center; + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +#github-chat-toggle:hover { + transform: scale(1.1); + box-shadow: 0 6px 24px rgba(0, 0, 0, 0.4); +} + +#github-chat-toggle svg { + width: 28px; + height: 28px; + fill: white; +} + +#github-chat-toggle .close-icon { + display: none; +} + +#github-chat-toggle.active .chat-icon { + display: none; +} + +#github-chat-toggle.active .close-icon { + display: block; +} + +#github-chat-panel { + position: fixed; + bottom: 92px; + right: 24px; + width: 320px; + height: 480px; + z-index: 2147483645; + border: none; + border-radius: 16px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); + overflow: hidden; + display: none; + transition: opacity 0.2s ease, transform 0.2s ease; +} + +#github-chat-panel.visible { + display: block; + animation: github-chat-slide-in 0.25s ease-out; +} + +@keyframes github-chat-slide-in { + from { + opacity: 0; + transform: translateY(16px) scale(0.95); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} diff --git a/chrome-extension/content.js b/chrome-extension/content.js new file mode 100644 index 0000000..a9b716a --- /dev/null +++ b/chrome-extension/content.js @@ -0,0 +1,66 @@ +// GitHub Chat - Content Script +// Injects a toggle button and chat iframe on github.com pages + +(function () { + 'use strict'; + + // Prevent double injection + if (document.getElementById('github-chat-toggle')) return; + + // Extract repo info from current URL + function getRepoInfo() { + const path = window.location.pathname.split('/').filter(Boolean); + if (path.length >= 2) { + return { owner: path[0], repo: path[1] }; + } + return null; + } + + // Create toggle button + const toggle = document.createElement('button'); + toggle.id = 'github-chat-toggle'; + toggle.title = 'Toggle GitHub Chat'; + toggle.innerHTML = ` + + + + + + + `; + + // Create iframe for chat + const panel = document.createElement('iframe'); + panel.id = 'github-chat-panel'; + const repoInfo = getRepoInfo(); + const chatUrl = chrome.runtime.getURL('chat.html'); + const params = repoInfo + ? `?owner=${encodeURIComponent(repoInfo.owner)}&repo=${encodeURIComponent(repoInfo.repo)}` + : ''; + panel.src = chatUrl + params; + + // Toggle visibility + function toggleChat() { + const isVisible = panel.classList.contains('visible'); + if (isVisible) { + panel.classList.remove('visible'); + toggle.classList.remove('active'); + } else { + panel.classList.add('visible'); + toggle.classList.add('active'); + } + } + + toggle.addEventListener('click', toggleChat); + + // Listen for messages from background script (toolbar icon click) + chrome.runtime.onMessage.addListener((message) => { + if (message.action === 'toggleChat') { + toggleChat(); + } + }); + + // Inject into page + document.body.appendChild(toggle); + document.body.appendChild(panel); +})(); diff --git a/chrome-extension/css/chat.css b/chrome-extension/css/chat.css new file mode 100644 index 0000000..423922e --- /dev/null +++ b/chrome-extension/css/chat.css @@ -0,0 +1,267 @@ +/* GitHub Chat Extension - Chat Panel Styles */ +*, +*::before, +*::after { + box-sizing: border-box; +} + +html, body { + height: 100%; + margin: 0; + padding: 0; + overflow: hidden; +} + +body { + background: #1a1a2e; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif; + font-size: 13px; + line-height: 1.4; + color: #c9d1d9; +} + +/* Chat Container */ +.chat { + display: flex; + flex-direction: column; + height: 100vh; + overflow: hidden; +} + +/* Chat Title */ +.chat-title { + flex: 0 0 auto; + display: flex; + align-items: center; + gap: 10px; + padding: 12px 16px; + background: linear-gradient(135deg, #044f48, #2a7561); + color: #fff; +} + +.chat-title-icon { + flex-shrink: 0; + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + background: rgba(255,255,255,0.15); + border-radius: 50%; +} + +.chat-title h1 { + font-size: 14px; + font-weight: 600; + margin: 0; +} + +.chat-title h2 { + font-size: 11px; + font-weight: 400; + margin: 0; + opacity: 0.7; +} + +/* Name Prompt */ +.name-prompt { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + padding: 24px; +} + +.name-prompt-inner { + text-align: center; + width: 100%; +} + +.name-prompt h3 { + font-size: 16px; + color: #e6edf3; + margin: 0 0 8px; +} + +.name-prompt p { + font-size: 12px; + color: #8b949e; + margin: 0 0 16px; +} + +.name-prompt input { + width: 100%; + padding: 10px 14px; + border: 1px solid #30363d; + border-radius: 8px; + background: #0d1117; + color: #e6edf3; + font-size: 13px; + outline: none; + margin-bottom: 12px; +} + +.name-prompt input:focus { + border-color: #2a7561; + box-shadow: 0 0 0 3px rgba(42, 117, 97, 0.3); +} + +.name-prompt button { + width: 100%; + padding: 10px; + border: none; + border-radius: 8px; + background: linear-gradient(135deg, #044f48, #2a7561); + color: white; + font-size: 13px; + font-weight: 600; + cursor: pointer; + transition: opacity 0.2s; +} + +.name-prompt button:hover { + opacity: 0.9; +} + +/* Messages */ +.messages { + flex: 1; + overflow: hidden; + position: relative; +} + +.messages-content { + height: 100%; + overflow-y: auto; + padding: 12px; +} + +.messages-content::-webkit-scrollbar { + width: 6px; +} + +.messages-content::-webkit-scrollbar-track { + background: transparent; +} + +.messages-content::-webkit-scrollbar-thumb { + background: rgba(255,255,255,0.15); + border-radius: 3px; +} + +.message { + display: flex; + flex-direction: column; + max-width: 85%; + margin-bottom: 8px; + padding: 8px 12px; + border-radius: 12px 12px 12px 4px; + background: #21262d; + position: relative; + animation: fadeIn 0.2s ease; +} + +.message-sender { + font-size: 11px; + font-weight: 600; + color: #58a6ff; + margin-bottom: 2px; +} + +.message-text { + word-break: break-word; +} + +.message-time { + font-size: 10px; + color: #484f58; + margin-top: 4px; + align-self: flex-end; +} + +.message.message-personal { + margin-left: auto; + background: linear-gradient(135deg, #044f48, #2a7561); + color: #fff; + border-radius: 12px 12px 4px 12px; +} + +.message.message-personal .message-time { + color: rgba(255,255,255,0.5); +} + +.btn-delete { + position: absolute; + top: 4px; + right: 4px; + background: none; + border: none; + color: rgba(255,255,255,0.3); + cursor: pointer; + font-size: 16px; + line-height: 1; + padding: 2px 4px; + border-radius: 4px; + display: none; +} + +.message.message-personal:hover .btn-delete { + display: block; +} + +.btn-delete:hover { + color: #f85149; + background: rgba(248, 81, 73, 0.15); +} + +/* Message Box */ +.message-box { + flex: 0 0 auto; + display: flex; + align-items: center; + gap: 8px; + padding: 12px; + background: #161b22; + border-top: 1px solid #30363d; +} + +.message-input { + flex: 1; + background: #0d1117; + border: 1px solid #30363d; + border-radius: 8px; + color: #e6edf3; + font-size: 13px; + padding: 8px 12px; + resize: none; + height: 36px; + outline: none; + font-family: inherit; +} + +.message-input:focus { + border-color: #2a7561; +} + +.message-submit { + flex-shrink: 0; + padding: 8px 16px; + border: none; + border-radius: 8px; + background: linear-gradient(135deg, #044f48, #2a7561); + color: white; + font-size: 12px; + font-weight: 600; + cursor: pointer; + transition: opacity 0.2s; +} + +.message-submit:hover { + opacity: 0.85; +} + +/* Animations */ +@keyframes fadeIn { + from { opacity: 0; transform: translateY(4px); } + to { opacity: 1; transform: translateY(0); } +} diff --git a/chrome-extension/css/normalize.css b/chrome-extension/css/normalize.css new file mode 100644 index 0000000..c38f248 --- /dev/null +++ b/chrome-extension/css/normalize.css @@ -0,0 +1 @@ +/*! normalize.css v4.0.0 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block}audio:not([controls]){display:none;height:0}progress{vertical-align:baseline}template,[hidden]{display:none}a{background-color:transparent}a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}img{border-style:none}svg:not(:root){overflow:hidden}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}button,input,select,textarea{font:inherit;margin:0}optgroup{font-weight:bold}button,input,select{overflow:visible}button,select{text-transform:none}button,[type="button"],[type="reset"],[type="submit"]{cursor:pointer}[disabled]{cursor:default}button,html [type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button:-moz-focusring,input:-moz-focusring{outline:1px dotted ButtonText}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto}[type="checkbox"],[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}[type="number"]::-webkit-inner-spin-button,[type="number"]::-webkit-outer-spin-button{height:auto}[type="search"]{-webkit-appearance:textfield}[type="search"]::-webkit-search-cancel-button,[type="search"]::-webkit-search-decoration{-webkit-appearance:none} diff --git a/chrome-extension/images/icon128.png b/chrome-extension/images/icon128.png new file mode 100644 index 0000000000000000000000000000000000000000..3bc129e4272b882875eba4f4bceec71a00c0ab3f GIT binary patch literal 1312 zcmZ`(ZA?>F7(S&~P+BZ*D#b$SV8y5d6sIDkx5(F=1eyibNaiXi)`II1gzCMr!9^}*r%UwTew_C_=RME+p7%Ll z4U+s^)`F-7007qJ53)XjJsN1C3}|nPN^S!X{A6=hM&Y40-_ZS!e-*#2E2&z{-LoW{ zt>Q*VM!Pu>%$++>)K_kNzA?LQW7mqavmHfKEs7+aAb#IIx}rq8up5g7vA0IoFJ)uc|5o9& zwp7-hQDlB-6!|@l$r0g(q0z_(2b7&mtblsipiWmBkMN>IdP!UuvXj#DumYIj>%21Z z6FfCIM0JuJ6}~EL8;o()%X*TW`eB1@UI7(kRh6cl&7x{Z1F|&Xiqut)F}2BYS1)_( z68!z-q;UF2_W+-bFfHjnq%X^+E|NE*XvBV8xjM)*=0RqQ+XkJ^dP8@Tv#K~QRChF@io2VFF9hW8Xu;|YKmbQa~3@(BUB~02k9e3D$s7jf6HY* zGpPml7RxFDe{Mj&cN@{{JZ@ZNnf9D?=xhr`wJw=2<(h=tBA+Q?n2GZ-#xR|tz(qUu zN@Q~A>{{YmFSsNcDcQ>>e18d1mkD2e%|)IMF;5eT#;_)=Cb-4qOG#x>y>yqqJKX5D zX{RT)tI8LOXWDD>^24l?fQ{TO+gnUt&PBe&{475hD5VE8U zR2BOc1O*IK6SWkAj!$C@qou;*O_0{kK$YP<=(PINIbf3Cm>1qKrvPLE6cq2j1K1=N zMdCGOV7V&X##^La10@``t$_h=R_~)H^8=Obq3*wyP$7LIp+cGvkf4te-&$X}8#z61 zmZ*xR&e9&?nM{-%B39DHp4Nk7$GCuaI4otvHi^gPZLt?HvG+lLXN4{jy1m#2;FF$< zKoTtnLH*RT{Tx7ac{^ljjP09l6rLjId!!-enVkN05eQS$M0;w@|AHj&8}#Kl=DEb7 zNqVehg(VslXxub5!quFIXVke25SfoUWTxMhayVgx6G4^=z$aQ+DESS(jAa(qy2#O) z!=8kbiV*V=Y!|Sn>TeIW9G!0iZY0`EonYnGKubRT^AT_lmxo79}986Pv{T^sJR{OMR%O%2}b zT+d=5lmCoYs{0J;Sw@iz-yv&X^KSyLQ0sak$w(;jQ*gPQ}s=uSCgdFmq5)C$h4 zH$^PdyW)#XHwNCmu_8bGmE*3WXPjjJ)TLNRGL{YR>9nQ>|^+jjK6_EpD=j3`njxgN@xNAvd?UJ literal 0 HcmV?d00001 diff --git a/chrome-extension/images/icon48.png b/chrome-extension/images/icon48.png new file mode 100644 index 0000000000000000000000000000000000000000..2688a6486eadd7cbcb25db73658c11f6bdd18eea GIT binary patch literal 519 zcmV+i0{H!jP)StpNzPj5nY~$UdT)ZDz^ryI zi0E4Pl6AdDd&OFgc#Hha+$n`kQVM6_I{0$bA{%|)O5Cy|v(j!7+Pv_U_?mTd!0R>q z*8L6Z(!rMrPrLmBo7{SvoEM|1@#~-|b$#`HxKm}hP zPX(Z}4Yq&|zCMmfU^19t1_J*l0G%qa1$6KypAiX6Mq}vp#?T#BC14F+2tXEam*7(Z(Axu9 zY{91lxP<=d!&!J5$i2;f0Xy)GNrr&#(#aM0#$+JBnOOA3%n5iAui?3XyYS~f00A1W z;YGZL7x5Z?CgCx>h}ZBUUc)D2{hTi%Uc=9)@Tt;fj5nzQ@(TtKAm1648PWg%002ov JPDHLkV1nAl?8^WE literal 0 HcmV?d00001 diff --git a/chrome-extension/manifest.json b/chrome-extension/manifest.json new file mode 100644 index 0000000..a9e2484 --- /dev/null +++ b/chrome-extension/manifest.json @@ -0,0 +1,34 @@ +{ + "manifest_version": 3, + "name": "GitHub Chat", + "description": "Chat with GitHub repository owners directly from any GitHub page", + "version": "1.0.0", + "icons": { + "16": "images/icon16.png", + "48": "images/icon48.png", + "128": "images/icon128.png" + }, + "content_scripts": [ + { + "matches": ["https://github.com/*"], + "js": ["content.js"], + "css": ["content.css"] + } + ], + "web_accessible_resources": [ + { + "resources": ["chat.html", "chat.js", "css/*", "images/*"], + "matches": ["https://github.com/*"] + } + ], + "action": { + "default_icon": { + "16": "images/icon16.png", + "48": "images/icon48.png" + }, + "default_title": "Toggle GitHub Chat" + }, + "background": { + "service_worker": "background.js" + } +}