feat: poc working

This commit is contained in:
2024-10-04 15:27:59 +03:00
parent 0d0a9bf63b
commit a0648b3a4e
11 changed files with 168 additions and 77 deletions

9
.editorconfig Normal file
View File

@@ -0,0 +1,9 @@
[*]
tab_width = 2
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false

1
.prettierignore Normal file
View File

@@ -0,0 +1 @@
gen/

15
.prettierrc Normal file
View File

@@ -0,0 +1,15 @@
{
"printWidth": 100,
"semi": false,
"singleQuote": true,
"trailingComma": "all",
"overrides": [
{
"files": "*.md",
"options": {
"printWidth": 100,
"proseWrap": "always"
}
}
]
}

18
eslint.config.mjs Normal file
View File

@@ -0,0 +1,18 @@
import eslint from '@eslint/js'
import tseslint from 'typescript-eslint'
export default [
...tseslint.config(eslint.configs.recommended, ...tseslint.configs.recommended),
{
rules: {
'no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
'@typescript-eslint/no-unused-vars': [
'warn',
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
],
},
},
{
ignores: ['node_modules/', 'build/', 'dist/', 'gen/'],
},
]

View File

@@ -54,6 +54,7 @@
"prettier": "^3.3.3",
"rimraf": "^6.0.1",
"typescript": "^5.6.2",
"typescript-eslint": "^8.8.0",
"unplugin-auto-import": "^0.18.3",
"vite": "^5.4.8",
"web-ext": "^8.3.0",

23
pnpm-lock.yaml generated
View File

@@ -87,6 +87,9 @@ importers:
typescript:
specifier: ^5.6.2
version: 5.6.2
typescript-eslint:
specifier: ^8.8.0
version: 8.8.0(eslint@9.11.1)(typescript@5.6.2)
unplugin-auto-import:
specifier: ^0.18.3
version: 0.18.3(rollup@4.24.0)
@@ -2978,6 +2981,15 @@ packages:
typedarray@0.0.6:
resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==}
typescript-eslint@8.8.0:
resolution: {integrity: sha512-BjIT/VwJ8+0rVO01ZQ2ZVnjE1svFBiRczcpr1t1Yxt7sT25VSbPfrJtDsQ8uQTy2pilX5nI9gwxhUyLULNentw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
typescript@5.6.2:
resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==}
engines: {node: '>=14.17'}
@@ -6323,6 +6335,17 @@ snapshots:
typedarray@0.0.6: {}
typescript-eslint@8.8.0(eslint@9.11.1)(typescript@5.6.2):
dependencies:
'@typescript-eslint/eslint-plugin': 8.8.0(@typescript-eslint/parser@8.8.0(eslint@9.11.1)(typescript@5.6.2))(eslint@9.11.1)(typescript@5.6.2)
'@typescript-eslint/parser': 8.8.0(eslint@9.11.1)(typescript@5.6.2)
'@typescript-eslint/utils': 8.8.0(eslint@9.11.1)(typescript@5.6.2)
optionalDependencies:
typescript: 5.6.2
transitivePeerDependencies:
- eslint
- supports-color
typescript@5.6.2: {}
ufo@1.5.4: {}

View File

@@ -1,16 +1,20 @@
// var process
import { resolve } from 'path'
import { bgCyan, black } from 'kolorist'
import react from '@vitejs/plugin-react'
const __dirname = import.meta.dirname
console.debug('process', global)
// process = global.process || {}
const env = import.meta.env || {}
// const env = process.env || import.meta.env || {}
export const fastRefresh = true
export const port = parseInt(process.env.PORT || '') || 3303
export const port = parseInt(env.PORT || '') || 3303
export const r = (...args: string[]) => resolve(__dirname, '..', ...args)
export const isDev = process.env.NODE_ENV !== 'production'
export const isDev = env.NODE_ENV !== 'production'
export const preambleCode = react.preambleCode.replace('__BASE__', '/')
export function log(name: string, message: string) {
// eslint-disable-next-line no-console
console.log(black(bgCyan(` ${name} `)), message)
}

View File

@@ -2,17 +2,21 @@ import { isFirefox, isForbiddenUrl } from '@/env'
// Firefox fetch files from cache instead of reloading changes from disk,
// hmr will not work as Chromium based browser
browser.webNavigation.onCommitted.addListener(({ tabId, frameId, url }) => {
browser.webNavigation.onCommitted.addListener(async ({ tabId, frameId, url }) => {
// Filter out non main window events.
if (frameId !== 0) return
if (isForbiddenUrl(url)) return
console.debug('Injecting', `${isFirefox ? '' : './'}dist/contentScripts/index.global.js`)
// inject the latest scripts
browser.tabs
.executeScript(tabId, {
try {
await browser.tabs.executeScript(tabId, {
file: `${isFirefox ? '' : '.'}/dist/contentScripts/index.global.js`,
runAt: 'document_end',
})
.catch(error => console.error(error))
} catch (error) {
console.error(error)
}
})

View File

@@ -1,5 +1,5 @@
import { sendMessage, onMessage } from 'webext-bridge'
import type { Tabs } from 'webextension-polyfill'
import { sendMessage } from 'webext-bridge/background'
import { Tabs } from 'webextension-polyfill'
// only on dev mode
if (import.meta.hot) {
@@ -10,47 +10,37 @@ if (import.meta.hot) {
}
browser.runtime.onInstalled.addListener((): void => {
// eslint-disable-next-line no-console
console.log('Extension installed')
})
let previousTabId = 0
console.log('Hello from background script')
// communication example: send previous tab title from background page
// see shim.d.ts for type declaration
browser.tabs.onActivated.addListener(async ({ tabId }) => {
if (!previousTabId) {
previousTabId = tabId
return
}
browser.commands.onCommand.addListener(async (cmd) => {
const tab = await browser.tabs
.query({ active: true, currentWindow: true })
.then((tabs) => tabs[0])
console.debug('command received', cmd, tab)
const resp = getResponse(cmd, tab)
if (!resp || !tab) return
let tab: Tabs.Tab
try {
tab = await browser.tabs.get(previousTabId)
previousTabId = tabId
} catch {
return
}
// eslint-disable-next-line no-console
console.log('previous tab', tab)
sendMessage(
'tab-prev',
{ title: tab.title },
{ context: 'content-script', tabId },
)
console.debug('sending message', resp, 'to tab', tab.id)
return sendMessage(resp.cmd, resp.payload!, {
context: 'content-script',
tabId: tab.id!,
})
})
onMessage('get-current-tab', async () => {
try {
const tab = await browser.tabs.get(previousTabId)
return {
title: tab?.title,
}
} catch {
return {
title: undefined,
}
function getResponse(msg: string, tab: Tabs.Tab) {
switch (msg) {
case 'copy-tab-url':
// copy tab url to clipboard
return { cmd: 'copy-text', payload: tab?.url }
case 'copy-tab-markdown':
// copy tab url and title in markdown format
return {
cmd: 'copy-text',
payload: `[${tab?.title}](${tab?.url})`,
}
}
})
return null
}

View File

@@ -1,30 +1,35 @@
import App from './App'
import * as ReactDOM from 'react-dom/client'
// import App from './App'
// import * as ReactDOM from 'react-dom/client'
import { onMessage } from 'webext-bridge/content-script'
// Firefox `browser.tabs.executeScript()` requires scripts return a primitive value
;(() => {
console.info('[vite-react-webext] Hello world from content script')
// Firefox `browser.tabs.executeScript()` requires scripts return a primitive value
; (() => {
console.info('[vite-react-webext] Hello world from content script')
// communication example: send previous tab title from background page
onMessage('tab-prev', ({ data }) => {
console.log(`[vite-react-webext] Navigate from page "${data.title}"`)
})
onMessage('copy-text', (payload) => {
console.debug('copy-text', payload)
setTimeout(async () => {
console.debug('Copying text', payload.data)
navigator.clipboard.writeText(payload.data as string)
}, 10)
// document.execCommand('copy', false, payload.data as string)
})
// mount component to context window
const container = document.createElement('div')
const root = document.createElement('div')
const styleEl = document.createElement('link')
const shadowDOM =
container.attachShadow?.({ mode: __DEV__ ? 'open' : 'closed' }) || container
styleEl.setAttribute('rel', 'stylesheet')
styleEl.setAttribute(
'href',
browser.runtime.getURL('dist/contentScripts/style.css'),
)
shadowDOM.appendChild(styleEl)
shadowDOM.appendChild(root)
document.body.appendChild(container)
const reactRoot = ReactDOM.createRoot(root)
reactRoot.render(<App />)
})()
// mount component to context window
try {
// const container = document.createElement('div')
// const root = document.createElement('div')
// const styleEl = document.createElement('link')
// const shadowDOM = container.attachShadow?.({ mode: __DEV__ ? 'open' : 'closed' }) || container
// styleEl.setAttribute('rel', 'stylesheet')
// styleEl.setAttribute('href', browser.runtime.getURL('dist/contentScripts/style.css'))
// shadowDOM.appendChild(styleEl)
// shadowDOM.appendChild(root)
// document.body.appendChild(container)
// const reactRoot = ReactDOM.createRoot(root)
// reactRoot.render(<App />)
console.info('[vite-react-webext] Component mounted')
} catch (error) {
console.error('Error mounting component', error)
}
})()

View File

@@ -32,7 +32,15 @@ export async function getManifest() {
48: './assets/icon-512.png',
128: './assets/icon-512.png',
},
permissions: ['tabs', 'storage', 'activeTab', 'http://*/', 'https://*/'],
permissions: [
// 'host',
'tabs',
'storage',
'activeTab',
'clipboardWrite',
'http://*/',
'https://*/',
],
content_scripts: [
{
matches: ['http://*/*', 'https://*/*'],
@@ -40,6 +48,22 @@ export async function getManifest() {
},
],
web_accessible_resources: ['dist/contentScripts/style.css'],
commands: {
'copy-tab-url': {
suggested_key: {
default: 'Ctrl+Shift+U',
mac: 'Command+Shift+U',
},
description: 'Copy the current tab URL',
},
'copy-tab-markdown': {
suggested_key: {
default: 'Ctrl+Shift+M',
mac: 'Command+Shift+M',
},
description: 'Copy the current tab URL and title in Markdown format',
},
},
}
if (isDev) {
@@ -49,13 +73,10 @@ export async function getManifest() {
delete manifest.content_scripts
manifest.permissions?.push('webNavigation')
const preambleCodeHash = crypto
.createHash('sha256')
.update(preambleCode)
.digest('base64')
const preambleCodeHash = crypto.createHash('sha256').update(preambleCode).digest('base64')
// this is required on dev for Vite script to load
manifest.content_security_policy = `script-src \'self\' 'sha256-${preambleCodeHash}' http://localhost:${port}; object-src \'self\'`
manifest.content_security_policy = `script-src 'self' 'sha256-${preambleCodeHash}' http://localhost:${port}; object-src 'self'`
}
return manifest