Skip to content

Commit

Permalink
webserial doesn't run on background tabs, but it also doesn't run unl…
Browse files Browse the repository at this point in the history
…ess it's in response to a user action! congratulations.
  • Loading branch information
fiatjaf committed Mar 11, 2023
1 parent d421473 commit e58d63f
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 90 deletions.
95 changes: 17 additions & 78 deletions extension/background.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,6 @@
/* globals chrome */

import browser from 'webextension-polyfill'
import {validateEvent, getEventHash} from 'nostr-tools'
import {Mutex} from 'async-mutex'
import {
callMethodOnDevice,
initDevice,
METHOD_PUBLIC_KEY,
METHOD_SIGN_MESSAGE,
isConnected
} from './serial'

import {
PERMISSIONS_REQUIRED,
Expand All @@ -21,68 +12,37 @@ let openPrompt = null
let promptMutex = new Mutex()
let releasePromptMutex = () => {}

browser.runtime.onInstalled.addListener((_, __, reason) => {
if (reason === 'install') browser.runtime.openOptionsPage()
chrome.runtime.onInstalled.addListener((_, __, reason) => {
if (reason === 'install') chrome.runtime.openOptionsPage()
})

browser.runtime.onMessage.addListener(async (req, sender) => {
chrome.runtime.onMessage.addListener(async (req, sender) => {
let {prompt, popup} = req

if (prompt) {
return handlePromptMessage(req)
} else if (popup) {
return handlePopupMessage(req, sender)
// forward to content script
let tabs = await chrome.tabs.query({active: true})
if (tabs.length) {
chrome.tabs.sendMessage(tabs[0].id, req)
}
} else {
return handleContentScriptMessage(req)
}
})

browser.runtime.onMessageExternal.addListener(
async ({type, params}, sender) => {
let extensionId = new URL(sender.url).host
return handleContentScriptMessage({type, params, host: extensionId})
}
)
chrome.runtime.onMessageExternal.addListener(async ({type, params}, sender) => {
let extensionId = new URL(sender.url).host
return handleContentScriptMessage({type, params, host: extensionId})
})

browser.windows.onRemoved.addListener(windowId => {
chrome.windows.onRemoved.addListener(windowId => {
if (openPrompt) {
handlePromptMessage({condition: 'no'}, null)
}
})

const connectionCallbacks = {
onConnect() {
console.log('wqwewqel')
chrome.action.setBadgeBackgroundColor({color: 'green'})
chrome.action.setBadgeText({text: 'on'})
browser.runtime.sendMessage({isConnected: true})
},
onDisconnect() {
chrome.action.setBadgeText({text: ''})
browser.runtime.sendMessage({isConnected: false})
},
onDone() {
chrome.action.setBadgeBackgroundColor({color: 'black'})
chrome.action.setBadgeText({text: 'done'})
browser.runtime.sendMessage({isConnected: false})
},
onError(error) {
chrome.action.setBadgeBackgroundColor({color: 'red'})
chrome.action.setBadgeText({text: 'err'})
browser.runtime.sendMessage({isConnected: false})
browser.runtime.sendMessage({serialError: error})
}
}

async function handlePopupMessage({method}) {
switch (method) {
case 'isConnected':
return isConnected()
case 'connect':
return initDevice(connectionCallbacks)
}
}

async function handleContentScriptMessage({type, params, host}) {
let level = await readPermissionLevel(host)

Expand All @@ -103,31 +63,10 @@ async function handleContentScriptMessage({type, params, host}) {

try {
switch (type) {
case 'getPublicKey': {
return callMethodOnDevice(METHOD_PUBLIC_KEY, [], connectionCallbacks)
}
case 'getRelays': {
let results = await browser.storage.local.get('relays')
let results = await chrome.storage.local.get('relays')
return results.relays || {}
}
case 'signEvent': {
let {event} = params

if (!event.pubkey) event.pubkey = callMethodOnDevice(METHOD_PUBLIC_KEY)
if (!event.id) event.id = getEventHash(event)
if (!validateEvent(event)) return {error: {message: 'invalid event'}}

event.sig = await callMethodOnDevice(METHOD_SIGN_MESSAGE, [event.id])
return event
}
case 'nip04.encrypt': {
// let {peer, plaintext} = params
throw new Error('not implemented')
}
case 'nip04.decrypt': {
// let {peer, ciphertext} = params
throw new Error('not implemented')
}
}
} catch (error) {
return {error: {message: error.message, stack: error.stack}}
Expand Down Expand Up @@ -156,7 +95,7 @@ function handlePromptMessage({id, condition, host, level}, sender) {
releasePromptMutex()

if (sender) {
browser.windows.remove(sender.tab.windowId)
chrome.windows.remove(sender.tab.windowId)
}
}

Expand All @@ -174,8 +113,8 @@ async function promptPermission(host, level, params) {
return new Promise((resolve, reject) => {
openPrompt = {resolve, reject}

browser.windows.create({
url: `${browser.runtime.getURL('prompt.html')}?${qs.toString()}`,
chrome.windows.create({
url: `${chrome.runtime.getURL('prompt.html')}?${qs.toString()}`,
type: 'popup',
width: 340,
height: 330
Expand Down
98 changes: 86 additions & 12 deletions extension/content-script.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import browser from 'webextension-polyfill'
/* globals chrome */

import {validateEvent, getEventHash} from 'nostr-tools'
import {
callMethodOnDevice,
initDevice,
METHOD_PUBLIC_KEY,
METHOD_SIGN_MESSAGE,
isConnected
} from './serial'

// inject the script that will provide window.nostr
let script = document.createElement('script')
script.setAttribute('async', 'false')
script.setAttribute('type', 'text/javascript')
script.setAttribute('src', browser.runtime.getURL('nostr-provider.js'))
script.setAttribute('src', chrome.runtime.getURL('nostr-provider.js'))
document.head.appendChild(script)

// listen for messages from that script
Expand All @@ -14,16 +23,42 @@ window.addEventListener('message', async message => {
if (!message.data.params) return
if (message.data.ext !== 'horse') return

// pass on to background
var response
try {
response = await browser.runtime.sendMessage({
type: message.data.type,
params: message.data.params,
host: location.host
})
} catch (error) {
response = {error}
// if we need the serial connection, handle it here (background.js doesn't have access)
switch (message.data.type) {
case 'getPublicKey': {
return callMethodOnDevice(METHOD_PUBLIC_KEY, [], connectionCallbacks)
}
case 'signEvent': {
let {event} = message.data.params

if (!event.pubkey) event.pubkey = callMethodOnDevice(METHOD_PUBLIC_KEY)
if (!event.id) event.id = getEventHash(event)
if (!validateEvent(event)) return {error: {message: 'invalid event'}}

event.sig = await callMethodOnDevice(METHOD_SIGN_MESSAGE, [event.id])
break
}
case 'nip04.encrypt': {
// let {peer, plaintext} = params
throw new Error('not implemented')
}
case 'nip04.decrypt': {
// let {peer, ciphertext} = params
throw new Error('not implemented')
}
default: {
// pass on to background
var response
try {
response = await chrome.runtime.sendMessage({
type: message.data.type,
params: message.data.params,
host: location.host
})
} catch (error) {
response = {error}
}
}
}

// return response
Expand All @@ -32,3 +67,42 @@ window.addEventListener('message', async message => {
message.origin
)
})

chrome.runtime.onMessage.addListener(async (req, sender) => {
if (req.popup) {
return handlePopupMessage(req, sender)
}
})

const connectionCallbacks = {
onConnect() {
console.log('wqwewqel')
chrome.action.setBadgeBackgroundColor({color: 'green'})
chrome.action.setBadgeText({text: 'on'})
chrome.runtime.sendMessage({isConnected: true})
},
onDisconnect() {
chrome.action.setBadgeText({text: ''})
chrome.runtime.sendMessage({isConnected: false})
},
onDone() {
chrome.action.setBadgeBackgroundColor({color: 'black'})
chrome.action.setBadgeText({text: 'done'})
chrome.runtime.sendMessage({isConnected: false})
},
onError(error) {
chrome.action.setBadgeBackgroundColor({color: 'red'})
chrome.action.setBadgeText({text: 'err'})
chrome.runtime.sendMessage({isConnected: false})
chrome.runtime.sendMessage({serialError: error})
}
}

async function handlePopupMessage({method}) {
switch (method) {
case 'isConnected':
return isConnected()
case 'connect':
return initDevice(connectionCallbacks)
}
}

0 comments on commit e58d63f

Please sign in to comment.