diff --git a/src/html/permissions.html b/src/html/permissions.html
index 792ff64..6de43fd 100644
--- a/src/html/permissions.html
+++ b/src/html/permissions.html
@@ -38,7 +38,7 @@ Auto Auth
href="homepage_url">Home Page
•
Get Support
+ href="https://github.com/cssnr/auto-auth?tab=readme-ov-file#support">Get Support
diff --git a/src/html/popup.html b/src/html/popup.html
index 86d57fe..d0e7769 100644
--- a/src/html/popup.html
+++ b/src/html/popup.html
@@ -14,6 +14,34 @@
+
+
+

+
+
+
+
+
+
+
+
-
-
-
+
-
+
+
+
+
+
Open Options
-
-
+
+
+
+
+
+
+
diff --git a/src/js/auth.js b/src/js/auth.js
index feaad03..89aa521 100644
--- a/src/js/auth.js
+++ b/src/js/auth.js
@@ -4,7 +4,6 @@ import { Hosts, linkClick, showHidePassword, showToast } from './export.js'
const searchParams = new URLSearchParams(window.location.search)
const url = new URL(searchParams.get('url'))
-
document.querySelectorAll('.host').forEach((el) => (el.textContent = url.host))
document.title = `Login for ${url.host}`
document.getElementById('favicon').href = `${url.origin}/favicon.ico`
diff --git a/src/js/export.js b/src/js/export.js
index 74dd2f8..ab7955e 100644
--- a/src/js/export.js
+++ b/src/js/export.js
@@ -1,5 +1,7 @@
// JS Exports
+export const githubURL = 'https://github.com/cssnr/auto-auth'
+
export class Hosts {
/** @type {[String]} */
static keys = [...'abcdefghijklmnopqrstuvwxyz0123456789']
@@ -323,13 +325,20 @@ export async function activateOrOpen(url, open = true) {
* @function updateManifest
*/
export function updateManifest() {
- const manifest = chrome.runtime.getManifest()
- document
- .querySelectorAll('.version')
- .forEach((el) => (el.textContent = manifest.version))
- document
- .querySelectorAll('[href="homepage_url"]')
- .forEach((el) => (el.href = manifest.homepage_url))
+ try {
+ const manifest = chrome.runtime.getManifest()
+ document.querySelectorAll('.version').forEach((el) => {
+ el.textContent = manifest.version
+ })
+ document.querySelectorAll('[href="version_url"]').forEach((el) => {
+ el.href = `${githubURL}/releases/tag/${manifest.version}`
+ })
+ document.querySelectorAll('[href="homepage_url"]').forEach((el) => {
+ el.href = manifest.homepage_url
+ })
+ } catch (e) {
+ console.log('Error updating manifest settings:', e)
+ }
}
/**
diff --git a/src/js/main.js b/src/js/main.js
index 217bb01..8dab34c 100644
--- a/src/js/main.js
+++ b/src/js/main.js
@@ -3,7 +3,9 @@
import { showToast } from './export.js'
if (typeof ClipboardJS !== 'undefined') {
- const clipboard = new ClipboardJS('.clip')
+ const clipboard = new ClipboardJS(
+ '[data-clipboard-text],[data-clipboard-target]'
+ )
clipboard.on('success', function (event) {
// console.debug('clipboard.success:', event)
const text = event.text.trim()
diff --git a/src/js/options.js b/src/js/options.js
index 8766065..13bc8d7 100644
--- a/src/js/options.js
+++ b/src/js/options.js
@@ -96,8 +96,8 @@ async function initOptions() {
console.debug('initOptions')
updateManifest()
- await setShortcuts()
- await checkPerms()
+ setShortcuts('#keyboard-shortcuts', true).then()
+ checkPerms().then()
// const { options, sites } = await chrome.storage.sync.get([
// 'options',
@@ -107,6 +107,7 @@ async function initOptions() {
const { options } = await chrome.storage.sync.get(['options'])
updateOptions(options)
backgroundChange(options.radioBackground)
+
const hosts = await Hosts.all()
// console.debug('hosts:', hosts)
updateTable(hosts)
@@ -227,20 +228,24 @@ function updateTable(data) {
*/
async function deleteHost(event) {
console.debug('deleteHost:', event)
- const host = event.currentTarget?.dataset?.value
- console.debug('host:', host)
- const confirm = event.currentTarget?.id !== 'confirm-delete'
- const { options } = await chrome.storage.sync.get(['options'])
- if (options.confirmDelete && !!confirm) {
- console.debug('Show Delete Modal')
- confirmDelete.dataset.value = host
- confirmDeleteHost.textContent = host
- deleteModal.show()
- return
+ try {
+ const host = event.currentTarget?.dataset?.value
+ console.debug('host:', host)
+ const confirm = event.currentTarget?.id !== 'confirm-delete'
+ const { options } = await chrome.storage.sync.get(['options'])
+ if (options.confirmDelete && !!confirm) {
+ console.debug('Show Delete Modal')
+ confirmDelete.dataset.value = host
+ confirmDeleteHost.textContent = host
+ deleteModal.show()
+ return
+ }
+ await Hosts.delete(host)
+ deleteModal.hide()
+ showToast(`Removed: ${host}`)
+ } catch (e) {
+ showToast(`Delete Error: ${e.message}`, 'danger')
}
- await Hosts.delete(host)
- deleteModal.hide()
- showToast(`Removed: ${host}`)
}
/**
@@ -275,31 +280,52 @@ async function editSubmit(event) {
console.debug('editSubmit:', event)
event.preventDefault()
event.stopPropagation()
- const hostname = editHostname.value.toLowerCase()
- const username = editUsername.value
- const password = editPassword.value
- // console.debug('hostname ,username, password:', hostname, username, password)
- if (
- hostname === editHostname.dataset.original &&
- username === editUsername.dataset.original &&
- password === editPassword.dataset.original
- ) {
+ try {
+ const hostname = getHost(editHostname.value)
+ const username = editUsername.value
+ const password = editPassword.value
+ // console.debug('hostname ,username, password:', hostname, username, password)
+ if (
+ hostname === editHostname.dataset.original &&
+ username === editUsername.dataset.original &&
+ password === editPassword.dataset.original
+ ) {
+ editModal.hide()
+ return showToast('No Changes Detected', 'warning')
+ }
+ // const { sites } = await chrome.storage.sync.get(['sites'])
+ // if (hostname !== editHostname.dataset.original) {
+ // delete sites[editHostname.dataset.original]
+ // }
+ // sites[hostname] = `${username}:${password}`
+ // await chrome.storage.sync.set({ sites })
+ await Hosts.edit(
+ editHostname.dataset.original,
+ hostname,
+ `${username}:${password}`
+ )
editModal.hide()
- return showToast('No Changes Detected', 'warning')
+ showToast(`Updated Host: ${hostname}`, 'success')
+ } catch (e) {
+ showToast(`Error saving credentials: ${e.message}`, 'danger')
}
- // const { sites } = await chrome.storage.sync.get(['sites'])
- // if (hostname !== editHostname.dataset.original) {
- // delete sites[editHostname.dataset.original]
- // }
- // sites[hostname] = `${username}:${password}`
- // await chrome.storage.sync.set({ sites })
- await Hosts.edit(
- editHostname.dataset.original,
- hostname,
- `${username}:${password}`
- )
- editModal.hide()
- showToast(`Updated Host: ${hostname}`, 'success')
+}
+
+/**
+ * @function getHost
+ * @param {String} hostname
+ * @return {String}
+ */
+function getHost(hostname) {
+ let host = hostname.toLowerCase().trim()
+ host = host.includes('://') ? host : 'https://' + host
+ console.debug('host:', host)
+ const url = new URL(host)
+ console.debug('url.host:', url.host)
+ if (!url.host) {
+ throw new Error(`Invalid Hostname: ${hostname}`)
+ }
+ return url.host
}
/**
@@ -351,29 +377,39 @@ async function exportHosts(event) {
async function hostsInputChange(event) {
console.debug('hostsInputChange:', event, hostsInput)
event.preventDefault()
- const fileReader = new FileReader()
- fileReader.onload = async function doBannedImport() {
- const results = JSON.parse(fileReader.result.toString())
- // console.debug('results:', results)
- // const { sites } = await chrome.storage.sync.get(['sites'])
- const hosts = {}
- let count = 0
- for (const [key, value] of Object.entries(results)) {
- count += 1
- if (typeof value === 'object') {
- const { username, password } = value
- hosts[key] = `${username}:${password}`
- } else if (typeof value === 'string') {
- // const [username, password] = value.split(':')
- hosts[key] = value
+ try {
+ const fileReader = new FileReader()
+ fileReader.onload = async function doBannedImport() {
+ const results = JSON.parse(fileReader.result.toString())
+ // console.debug('results:', results)
+ // const { sites } = await chrome.storage.sync.get(['sites'])
+ const hosts = {}
+ let count = 0
+ for (const [key, value] of Object.entries(results)) {
+ try {
+ if (typeof value === 'object') {
+ const { username, password } = value
+ hosts[key] = `${username}:${password}`
+ } else if (typeof value === 'string') {
+ // const [username, password] = value.split(':')
+ hosts[key] = value
+ }
+ count += 1
+ } catch (e) {
+ console.log(`Error processing: ${key}`, 'color: Red')
+ }
}
+ // console.debug('hosts:', hosts)
+ // await chrome.storage.sync.set({ sites })
+ await Hosts.update(hosts)
+ const total = Object.keys(results).length
+ showToast(`Imported/Updated ${count}/${total} Hosts.`, 'success')
}
- // console.debug('hosts:', hosts)
- // await chrome.storage.sync.set({ sites })
- await Hosts.update(hosts)
- showToast(`Imported/Updated ${count} Hosts.`, 'success')
+ fileReader.readAsText(hostsInput.files[0])
+ } catch (e) {
+ console.log('Import error:', e)
+ showToast(`Import Error: ${e.message}`, 'warning')
}
- fileReader.readAsText(hostsInput.files[0])
}
/**
@@ -430,27 +466,51 @@ async function onChanged(changes, namespace) {
/**
* Set Keyboard Shortcuts
* @function setShortcuts
- * @param {String} selector
+ * @param {String} [selector]
+ * @param {Boolean} [action]
*/
-async function setShortcuts(selector = '#keyboard-shortcuts') {
+async function setShortcuts(selector = '#keyboard-shortcuts', action = false) {
if (!chrome.commands) {
return console.debug('Skipping: chrome.commands')
}
const table = document.querySelector(selector)
+ if (!table) {
+ return console.warn(`${selector} table not found`)
+ }
+ table.classList.remove('d-none')
const tbody = table.querySelector('tbody')
const source = table.querySelector('tfoot > tr').cloneNode(true)
const commands = await chrome.commands.getAll()
for (const command of commands) {
- // console.debug('command:', command)
- const row = source.cloneNode(true)
- // TODO: Chrome does not parse the description for _execute_action in manifest.json
- let description = command.description
- if (!description && command.name === '_execute_action') {
- description = 'Show Popup'
+ try {
+ // console.debug('command:', command)
+ const row = source.cloneNode(true)
+ let description = command.description
+ // Note: Chrome does not parse the description for _execute_action in manifest.json
+ if (!description && command.name === '_execute_action') {
+ description = 'Show Popup Action'
+ }
+ row.querySelector('.description').textContent = description
+ row.querySelector('kbd').textContent = command.shortcut || 'Not Set'
+ tbody.appendChild(row)
+ } catch (e) {
+ console.warn('Error adding command:', command, e)
+ }
+ }
+ if (action) {
+ try {
+ const userSettings = await chrome.action.getUserSettings()
+ const row = source.cloneNode(true)
+ row.querySelector('i').className = 'fa-solid fa-puzzle-piece me-1'
+ row.querySelector('.description').textContent =
+ 'Toolbar Icon Pinned'
+ row.querySelector('kbd').textContent = userSettings.isOnToolbar
+ ? 'Yes'
+ : 'No'
+ tbody.appendChild(row)
+ } catch (e) {
+ console.log('Error adding pinned setting:', e)
}
- row.querySelector('.description').textContent = description
- row.querySelector('kbd').textContent = command.shortcut || 'Not Set'
- tbody.appendChild(row)
}
}
diff --git a/src/js/popup.js b/src/js/popup.js
index 23bebcd..e7ad849 100644
--- a/src/js/popup.js
+++ b/src/js/popup.js
@@ -32,7 +32,7 @@ const confirmDeleteHost = document.getElementById('delete-host')
const deleteModal = new bootstrap.Modal('#delete-modal')
confirmDelete.addEventListener('click', deleteHost)
-const hostDiv = document.getElementById('host')
+const hostnameEl = document.getElementById('hostname')
const deleteSaved = document.getElementById('delete-saved')
/**
@@ -68,20 +68,20 @@ async function initPopup() {
const url = new URL(tab.url)
const creds = await Hosts.get(url.host)
if (creds) {
- hostDiv.classList.add('border-success')
- hostDiv.textContent = url.host
+ hostnameEl.classList.add('border-success')
+ hostnameEl.textContent = url.host
deleteSaved.classList.remove('d-none')
deleteSaved.dataset.value = url.host
deleteSaved.addEventListener('click', deleteHost)
confirmDelete.dataset.value = url.host
confirmDeleteHost.textContent = url.host
} else {
- hostDiv.textContent = 'No Credentials Found for Tab.'
- hostDiv.classList.remove('border-success')
+ hostnameEl.textContent = 'No Credentials Found for Tab.'
+ hostnameEl.classList.remove('border-success')
deleteSaved.classList.add('d-none')
}
} else {
- hostDiv.classList.add('border-danger-subtle')
+ hostnameEl.classList.add('border-danger-subtle')
}
}
diff --git a/src/js/service-worker.js b/src/js/service-worker.js
index 1de9996..cede90f 100644
--- a/src/js/service-worker.js
+++ b/src/js/service-worker.js
@@ -1,6 +1,6 @@
// JS Background Service Worker
-import { Hosts, checkPerms, showPanel } from './export.js'
+import { Hosts, checkPerms, showPanel, githubURL } from './export.js'
chrome.runtime.onStartup.addListener(onStartup)
chrome.runtime.onInstalled.addListener(onInstalled)
@@ -45,10 +45,13 @@ async function onAuthRequired(details, callback) {
// console.debug('url.host:', url.host)
const hijackRequest = (failed = false) => {
- const color = failed ? 'Yellow' : 'Lime'
+ if (details.tabId === -1) {
+ console.warn(`Unable to process tab:`, details)
+ return callback()
+ }
console.log(
`Cancel Request and Hijack w/ failed: %c${failed}`,
- `color: ${color}`
+ `color: ${failed ? 'Yellow' : 'Lime'}`
)
const auth = new URL(chrome.runtime.getURL('/html/auth.html'))
auth.searchParams.append('url', details.url)
@@ -162,7 +165,6 @@ async function onStartup() {
*/
async function onInstalled(details) {
console.log('onInstalled:', details)
- const githubURL = 'https://github.com/cssnr/auto-auth'
// const uninstallURL = new URL('https://link-extractor.cssnr.com/uninstall/')
const options = await setDefaultOptions({
tempDisabled: false,
@@ -285,7 +287,7 @@ async function onChanged(changes, namespace) {
createContextMenus()
} else {
console.info('Disabled contextMenu...')
- chrome.contextMenus.removeAll()
+ chrome.contextMenus?.removeAll()
}
}
if (oldValue.tempDisabled !== newValue.tempDisabled) {
@@ -369,6 +371,9 @@ async function updateIcon(options) {
* @function createContextMenus
*/
function createContextMenus() {
+ if (!chrome.contextMenus) {
+ return console.debug('Skipping: chrome.contextMenus')
+ }
console.debug('createContextMenus')
chrome.contextMenus.removeAll()
/** @type {Array[String[], String, String, String]} */
diff --git a/src/js/theme.js b/src/js/theme.js
index 8d61db4..ff5976d 100644
--- a/src/js/theme.js
+++ b/src/js/theme.js
@@ -55,6 +55,14 @@
})
}
+ window.addEventListener('storage', (event) => {
+ // console.log('storage:', event)
+ if (event.key === 'theme') {
+ setTheme(event.newValue)
+ showActiveTheme(event.newValue)
+ }
+ })
+
window
.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', () => {
diff --git a/tests/test.js b/tests/test.js
index 78c7047..674df4e 100644
--- a/tests/test.js
+++ b/tests/test.js
@@ -88,9 +88,12 @@ async function getPage(browser, name, size) {
// Popup
await worker.evaluate('chrome.action.openPopup();')
- const popup = await getPage(browser, 'popup.html')
+ let popup = await getPage(browser, 'popup.html')
console.log('popup:', popup)
await popup.waitForNetworkIdle()
+ console.log('innerWidth:', await popup.evaluate('window.innerWidth'))
+ console.log('innerHeight:', await popup.evaluate('window.innerHeight'))
+ console.log('userAgent:', await popup.evaluate('navigator.userAgent'))
await popup.screenshot(ssOptions('popup'))
await popup.locator('[href="../html/options.html"]').click()
@@ -153,6 +156,12 @@ async function getPage(browser, name, size) {
await page.waitForNetworkIdle()
await page.screenshot(ssOptions('success'))
+ await worker.evaluate('chrome.action.openPopup();')
+ popup = await getPage(browser, 'popup.html')
+ console.log('popup:', popup)
+ await popup.waitForNetworkIdle()
+ await popup.screenshot(ssOptions('popup'))
+
try {
// Intercepting auth throws: Error: net::ERR_ABORTED
await page.goto('https://httpbin.org/basic-auth/guest/guest')