Skip to content

Commit

Permalink
Protection debugging over websocket (#2519)
Browse files Browse the repository at this point in the history
* WIP: forward devtools messages to the debugger tool

* Post tab changes to the debugger

* Remote reload

* Set internal user for debug builds
  • Loading branch information
sammacbeth authored Apr 30, 2024
1 parent f65b74f commit 1844492
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 12 deletions.
91 changes: 81 additions & 10 deletions shared/js/background/components/debugger-connection.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import browser from 'webextension-polyfill'
import { registerMessageHandler } from '../message-handlers'
import { getFromSessionStorage, removeFromSessionStorage, setToSessionStorage } from '../wrapper'
import { getBrowserName } from '../utils'
import { getExtensionVersion, getFromSessionStorage, removeFromSessionStorage, setToSessionStorage } from '../wrapper'
import { registerDebugHandler } from '../devtools'

/**
* @typedef {import('./tds').default} TDSStorage
Expand All @@ -25,6 +28,8 @@ export default class DebuggerConnection {
constructor ({ tds }) {
this.init()
this.tds = tds
this.socket = null
this.subscribedTabs = new Set()
registerMessageHandler('getDebuggingSettings', getDebuggerSettings)
registerMessageHandler('enableDebugging', ({ configURLOverride, debuggerConnection }) => {
return this.enableDebugging(configURLOverride, debuggerConnection)
Expand All @@ -38,20 +43,75 @@ export default class DebuggerConnection {
this.configURLOverride = configURLOverride
this.debuggerConnectionEnabled = debuggerConnection
if (this.configURLOverride && this.debuggerConnectionEnabled) {
const url = new URL('./status', this.configURLOverride)
const url = new URL('./debugger/extension', this.configURLOverride.replace(/https?:/, 'ws:'))
url.searchParams.append('browserName', getBrowserName())
url.searchParams.append('version', getExtensionVersion())
let lastUpdate = 0
this.eventSource = new EventSource(url.href)
this.eventSource.onmessage = event => {
const status = JSON.parse(event.data)
console.log('debugger message', status)
if (status.lastBuild > lastUpdate) {
lastUpdate = status.lastBuild
this.tds.config.checkForUpdates(true)
this.socket = new WebSocket(url.href)
this.socket.addEventListener('message', (event) => {
const { messageType, payload } = JSON.parse(event.data)
console.log('debugger message', event.data)
if (messageType === 'status') {
if (payload.lastBuild > lastUpdate) {
lastUpdate = payload.lastBuild
this.tds.config.checkForUpdates(true)
}
} else if (messageType === 'subscribe') {
const { tabId } = payload
if (!this.subscribedTabs.has(tabId)) {
this.subscribedTabs.add(tabId)
this.forwardDebugMessagesForTab(tabId)
}
} else if (messageType === 'reloadTab') {
const { tabId } = payload
browser.tabs.reload(tabId)
}
})
this.socket.addEventListener('close', () => {
this.socket = null
setTimeout(() => this.init(), 5000)
})

// rate limit sending tabs to 1 message per second
let lastTabSend = 0
let nextTabSend = null
const sendTabs = async () => {
if (nextTabSend) {
return
}
if (Date.now() - lastTabSend < 1000) {
nextTabSend = setTimeout(() => {
nextTabSend = null
sendTabs()
}, 1000)
return
}
lastTabSend = Date.now()
const tabs = await browser.tabs.query({})
this.socket?.send(JSON.stringify({
messageType: 'tabs',
payload: tabs.sort((a, b) => (a.lastAccessed || 0) - (b.lastAccessed || 0))
}))
}

this.socket.addEventListener('open', async () => {
sendTabs()

browser.tabs.onUpdated.addListener(() => {
sendTabs()
})

this.subscribedTabs.forEach((tabId) => {
this.forwardDebugMessagesForTab(tabId)
})
})
}
}

async isActive () {
return this.socket !== null
}

async enableDebugging (url, debuggerConnection = false) {
await Promise.all([
setToSessionStorage('configURLOverride', url),
Expand All @@ -70,6 +130,17 @@ export default class DebuggerConnection {
removeFromSessionStorage('configURLOverride'),
removeFromSessionStorage('debuggerConnection')
])
this.eventSource?.close()
this.socket?.close()
}

forwardDebugMessagesForTab (tabId) {
registerDebugHandler(tabId, (payload) => {
if (this.socket) {
this.socket.send(JSON.stringify({
messageType: 'devtools',
payload
}))
}
})
}
}
3 changes: 2 additions & 1 deletion shared/js/background/components/internal-user-detector.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* global DEBUG */
import browser from 'webextension-polyfill'
import { registerMessageHandler } from '../message-handlers'

Expand All @@ -9,7 +10,7 @@ const SETTINGS_KEY = 'isInternalUser'
* @param {import('../settings.js')} settings
*/
export function isInternalUser (settings) {
return settings.getSetting(SETTINGS_KEY) || false
return settings.getSetting(SETTINGS_KEY) || DEBUG
}

export default class InternalUserDetector {
Expand Down
10 changes: 9 additions & 1 deletion shared/js/background/devtools.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,16 @@ const { removeBroken } = require('./utils')
// background ServiceWorker will become active again and the onConnect
// event will fire again.
const ports = new Map()
const debugHandlers = new Map()

export function init () {
browser.runtime.onConnect.addListener(connected)
}

export function registerDebugHandler (tabId, fn) {
debugHandlers.set(tabId, fn)
}

/**
* Serialize a subset of the tab object to be sent to the panel
* @param {Object} tab
Expand Down Expand Up @@ -131,8 +136,11 @@ export function postMessage (tabId, action, message) {
if (ports.has(tabId)) {
ports.get(tabId).postMessage(JSON.stringify({ tabId, action, message }))
}
if (debugHandlers.has(tabId)) {
debugHandlers.get(tabId)({ tabId, action, message })
}
}

export function isActive (tabId) {
return ports.has(tabId)
return ports.has(tabId) || debugHandlers.has(tabId)
}

0 comments on commit 1844492

Please sign in to comment.