chore: upgrade wxt
This commit is contained in:
parent
9fca355c99
commit
0571682e73
18 changed files with 2195 additions and 1801 deletions
|
@ -34,6 +34,8 @@
|
|||
"prettier/prettier": "error",
|
||||
"react/prop-types": "off",
|
||||
"import/order": "error",
|
||||
"import/no-absolute-path": "off",
|
||||
"n/no-callback-literal": "off",
|
||||
"@typescript-eslint/naming-convention": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
|
@ -43,7 +45,6 @@
|
|||
"@typescript-eslint/restrict-template-expressions": "off",
|
||||
"@typescript-eslint/no-misused-promises": "off",
|
||||
"@typescript-eslint/consistent-type-assertions": "off",
|
||||
"import/no-absolute-path": "off",
|
||||
"@typescript-eslint/no-base-to-string": "off",
|
||||
"@typescript-eslint/no-unused-vars": "warn"
|
||||
}
|
||||
|
|
74
package.json
74
package.json
|
@ -6,7 +6,7 @@
|
|||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "wxt",
|
||||
"dev:signaling": "y-webrtc-signaling",
|
||||
"dev:server": "y-webrtc-signaling",
|
||||
"dev:firefox": "wxt -b firefox",
|
||||
"build": "wxt build",
|
||||
"build:firefox": "wxt build -b firefox",
|
||||
|
@ -44,8 +44,8 @@
|
|||
},
|
||||
"homepage": "https://github.com/molvqingtai/WebChat#readme",
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.3.2",
|
||||
"@perfsee/jsonr": "^1.8.4",
|
||||
"@hookform/resolvers": "^3.3.4",
|
||||
"@perfsee/jsonr": "^1.12.2",
|
||||
"@radix-ui/react-avatar": "^1.0.4",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||
"@radix-ui/react-hover-card": "^1.0.7",
|
||||
|
@ -58,61 +58,61 @@
|
|||
"@radix-ui/react-switch": "^1.0.3",
|
||||
"@tailwindcss/typography": "^0.5.10",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"date-fns": "^2.30.0",
|
||||
"clsx": "^2.1.0",
|
||||
"date-fns": "^3.3.1",
|
||||
"idb-keyval": "^6.2.1",
|
||||
"lucide-react": "^0.294.0",
|
||||
"nanoid": "^5.0.4",
|
||||
"peerjs": "^1.5.1",
|
||||
"lucide-react": "^0.336.0",
|
||||
"nanoid": "^5.0.6",
|
||||
"peerjs": "^1.5.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "^7.48.2",
|
||||
"react-hook-form": "^7.50.1",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-nice-avatar": "^1.5.0",
|
||||
"react-use": "^17.4.2",
|
||||
"react-use": "^17.5.0",
|
||||
"remark-breaks": "^4.0.0",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"remesh": "^4.2.0",
|
||||
"remesh": "^4.2.1",
|
||||
"remesh-logger": "^4.1.0",
|
||||
"remesh-react": "^4.1.0",
|
||||
"remesh-yjs": "^4.1.0",
|
||||
"remesh-react": "^4.1.2",
|
||||
"rxjs": "^7.8.1",
|
||||
"sonner": "^1.2.4",
|
||||
"tailwind-merge": "^2.1.0",
|
||||
"type-fest": "^4.8.3",
|
||||
"valibot": "^0.22.0",
|
||||
"y-webrtc": "^10.2.6"
|
||||
"sonner": "^1.4.0",
|
||||
"tailwind-merge": "^2.2.1",
|
||||
"type-fest": "^4.10.3",
|
||||
"unstorage": "^1.10.1",
|
||||
"valibot": "^0.29.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^18.4.3",
|
||||
"@commitlint/config-conventional": "^18.4.3",
|
||||
"@types/node": "^20.10.3",
|
||||
"@types/react": "^18.2.41",
|
||||
"@types/react-dom": "^18.2.17",
|
||||
"@commitlint/cli": "^18.6.1",
|
||||
"@commitlint/config-conventional": "^18.6.2",
|
||||
"@types/node": "^20.11.20",
|
||||
"@types/react": "^18.2.57",
|
||||
"@types/react-dom": "^18.2.19",
|
||||
"@types/webextension-polyfill": "^0.10.7",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.55.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"eslint-plugin-import": "^2.29.0",
|
||||
"eslint-plugin-n": "^16.3.1",
|
||||
"eslint-plugin-prettier": "^5.0.1",
|
||||
"eslint-config-standard-with-typescript": "^43.0.1",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-n": "^16.6.2",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-tailwindcss": "^3.13.0",
|
||||
"husky": "^8.0.3",
|
||||
"lint-staged": "^15.2.0",
|
||||
"eslint-plugin-tailwindcss": "^3.14.3",
|
||||
"husky": "^9.0.11",
|
||||
"lint-staged": "^15.2.2",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss": "^8.4.32",
|
||||
"prettier": "^3.1.0",
|
||||
"postcss": "^8.4.35",
|
||||
"prettier": "^3.2.5",
|
||||
"rimraf": "^5.0.5",
|
||||
"tailwindcss": "^3.3.6",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"typescript": "^5.3.2",
|
||||
"typescript": "^5.3.3",
|
||||
"webext-bridge": "^6.0.1",
|
||||
"wxt": "^0.10.4"
|
||||
"wxt": "^0.17.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx}": "eslint --fix"
|
||||
|
|
3565
pnpm-lock.yaml
3565
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
@ -1,5 +1,5 @@
|
|||
import { browser } from 'wxt/browser'
|
||||
import { defineBackground } from 'wxt/client'
|
||||
import { defineBackground } from 'wxt/sandbox'
|
||||
|
||||
export default defineBackground({
|
||||
// Set manifest options
|
||||
|
|
|
@ -3,33 +3,34 @@ import { createRoot } from 'react-dom/client'
|
|||
import { Remesh } from 'remesh'
|
||||
import { RemeshRoot } from 'remesh-react'
|
||||
import { RemeshLogger } from 'remesh-logger'
|
||||
import { defineContentScript, createContentScriptUi } from 'wxt/client'
|
||||
import * as Y from 'yjs'
|
||||
import { RemeshYjs, RemeshYjsExtern } from 'remesh-yjs'
|
||||
import { WebrtcProvider } from 'y-webrtc'
|
||||
import { defineContentScript } from 'wxt/sandbox'
|
||||
import { createShadowRootUi } from 'wxt/client'
|
||||
|
||||
import App from './App'
|
||||
import { IndexDBStorageImpl, BrowserSyncStorageImpl } from '@/impl/Storage'
|
||||
|
||||
import { PeerClientImpl } from '@/impl/PeerClient'
|
||||
import '@/assets/styles/tailwind.css'
|
||||
|
||||
export default defineContentScript({
|
||||
cssInjectionMode: 'ui',
|
||||
matches: ['*://*.example.com/*', '*://*.google.com/*', '*://*.v2ex.com/*'],
|
||||
async main(ctx) {
|
||||
const doc = new Y.Doc()
|
||||
// eslint-disable-next-line no-new
|
||||
new WebrtcProvider(__NAME__, doc, { signaling: ['ws://localhost:4444'] })
|
||||
|
||||
const store = Remesh.store({
|
||||
externs: [IndexDBStorageImpl, BrowserSyncStorageImpl, RemeshYjsExtern.impl({ doc })],
|
||||
externs: [IndexDBStorageImpl, BrowserSyncStorageImpl, PeerClientImpl],
|
||||
inspectors: [RemeshLogger()]
|
||||
})
|
||||
|
||||
const ui = await createContentScriptUi(ctx, {
|
||||
const ui = await createShadowRootUi(ctx, {
|
||||
name: __NAME__,
|
||||
type: 'overlay',
|
||||
mount(container) {
|
||||
const root = createRoot(container)
|
||||
position: 'inline',
|
||||
// anchor: 'body',
|
||||
// append: 'first',
|
||||
onMount: (container) => {
|
||||
const app = document.createElement('div')
|
||||
app.id = 'app'
|
||||
container.append(app)
|
||||
|
||||
const root = createRoot(app)
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<RemeshRoot store={store}>
|
||||
|
@ -39,8 +40,8 @@ export default defineContentScript({
|
|||
)
|
||||
return root
|
||||
},
|
||||
onRemove(root) {
|
||||
root.unmount()
|
||||
onRemove: (root) => {
|
||||
root?.unmount()
|
||||
}
|
||||
})
|
||||
ui.mount()
|
||||
|
|
|
@ -8,6 +8,6 @@
|
|||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="./index.tsx"></script>
|
||||
<script type="module" src="./main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -2,14 +2,19 @@ import { Remesh } from 'remesh'
|
|||
import { ListModule } from 'remesh/modules/list'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { from, map, tap, merge } from 'rxjs'
|
||||
import { RemeshYjs } from 'remesh-yjs'
|
||||
import { IndexDBStorageExtern } from './externs/Storage'
|
||||
import { PeerClientExtern } from './externs/PeerClient'
|
||||
import { callbackToObservable, stringToHex } from '@/utils'
|
||||
|
||||
const hostRoomId = stringToHex(document.location.host)
|
||||
|
||||
const MessageListDomain = Remesh.domain({
|
||||
name: 'MessageListDomain',
|
||||
impl: (domain) => {
|
||||
const storage = domain.getExtern(IndexDBStorageExtern)
|
||||
const peerClient = domain.getExtern(PeerClientExtern)
|
||||
const storageKey = `MESSAGE_LIST` as const
|
||||
peerClient.connect(hostRoomId)
|
||||
|
||||
const MessageListModule = ListModule<Message>(domain, {
|
||||
name: 'MessageListModule',
|
||||
|
@ -100,17 +105,31 @@ const MessageListDomain = Remesh.domain({
|
|||
}
|
||||
})
|
||||
|
||||
RemeshYjs(domain, {
|
||||
key: 'MessageList',
|
||||
dataType: 'array',
|
||||
onSend: ({ get }): Message[] => {
|
||||
return get(ListQuery())
|
||||
},
|
||||
onReceive: (_, messages: Message[]) => {
|
||||
return InitListCommand(messages)
|
||||
domain.effect({
|
||||
name: 'FormStateToPeerClientEffect',
|
||||
impl: ({ fromEvent }) => {
|
||||
const createItem$ = fromEvent(CreateItemEvent).pipe(
|
||||
tap(async (message) => {
|
||||
await peerClient.sendMessage(JSON.stringify(message))
|
||||
})
|
||||
)
|
||||
return merge(createItem$).pipe(map(() => null))
|
||||
}
|
||||
})
|
||||
|
||||
// domain.effect({
|
||||
// name: 'FormPeerClientToStateEffect',
|
||||
// impl: () => {
|
||||
// return callbackToObservable(peerClient.onMessage.bind(peerClient)).pipe(
|
||||
// map((message) => {
|
||||
// console.log(message)
|
||||
// // debugger
|
||||
// return CreateItemCommand(message)
|
||||
// })
|
||||
// )
|
||||
// }
|
||||
// })
|
||||
|
||||
return {
|
||||
query: {
|
||||
ItemQuery,
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { Remesh } from 'remesh'
|
||||
import { forkJoin, from, map, merge, switchMap, tap } from 'rxjs'
|
||||
import { BrowserSyncStorageExtern } from './externs/Storage'
|
||||
import { isNullish, storageToObservable } from '@/utils'
|
||||
import { isNullish } from '@/utils'
|
||||
import callbackToObservable from '@/utils/callbackToObservable'
|
||||
|
||||
const UserInfoDomain = Remesh.domain({
|
||||
name: 'UserInfoDomain',
|
||||
|
@ -104,7 +105,7 @@ const UserInfoDomain = Remesh.domain({
|
|||
domain.effect({
|
||||
name: 'WatchStorageToStateEffect',
|
||||
impl: () => {
|
||||
return storageToObservable(storage).pipe(
|
||||
return callbackToObservable(storage.watch, storage.unwatch).pipe(
|
||||
switchMap(() => {
|
||||
return forkJoin({
|
||||
id: from(storage.get<UserInfo['id']>(storageKeys.USER_INFO_ID)),
|
||||
|
|
25
src/domain/externs/PeerClient.ts
Normal file
25
src/domain/externs/PeerClient.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { Remesh } from 'remesh'
|
||||
|
||||
export interface PeerClient {
|
||||
connect: (id: string) => Promise<void>
|
||||
sendMessage: (message: string) => Promise<void>
|
||||
onMessage: (callback: (message: string) => void) => void
|
||||
close: () => Promise<void> | void
|
||||
}
|
||||
|
||||
export const PeerClientExtern = Remesh.extern<PeerClient>({
|
||||
default: {
|
||||
connect: async () => {
|
||||
throw new Error('"connect" not implemented.')
|
||||
},
|
||||
sendMessage: async () => {
|
||||
throw new Error('"sendMessage" not implemented.')
|
||||
},
|
||||
onMessage: () => {
|
||||
throw new Error('"onMessage" not implemented.')
|
||||
},
|
||||
close: () => {
|
||||
throw new Error('"close" not implemented.')
|
||||
}
|
||||
}
|
||||
})
|
|
@ -20,22 +20,22 @@ export const IndexDBStorageExtern = Remesh.extern<Storage>({
|
|||
default: {
|
||||
name: 'STORAGE',
|
||||
get: async () => {
|
||||
throw new Error('"get" not implemented')
|
||||
throw new Error('"get" not implemented.')
|
||||
},
|
||||
set: async () => {
|
||||
throw new Error('"set" not implemented')
|
||||
throw new Error('"set" not implemented.')
|
||||
},
|
||||
remove: async () => {
|
||||
throw new Error('"remove" not implemented')
|
||||
throw new Error('"remove" not implemented.')
|
||||
},
|
||||
clear: async () => {
|
||||
throw new Error('"clear" not implemented')
|
||||
throw new Error('"clear" not implemented.')
|
||||
},
|
||||
watch: () => {
|
||||
throw new Error('"watch" not implemented')
|
||||
throw new Error('"watch" not implemented.')
|
||||
},
|
||||
unwatch: () => {
|
||||
throw new Error('"unwatch" not implemented')
|
||||
throw new Error('"unwatch" not implemented.')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -44,22 +44,22 @@ export const BrowserSyncStorageExtern = Remesh.extern<Storage>({
|
|||
default: {
|
||||
name: 'STORAGE',
|
||||
get: async () => {
|
||||
throw new Error('"get" not implemented')
|
||||
throw new Error('"get" not implemented.')
|
||||
},
|
||||
set: async () => {
|
||||
throw new Error('"set" not implemented')
|
||||
throw new Error('"set" not implemented.')
|
||||
},
|
||||
remove: async () => {
|
||||
throw new Error('"remove" not implemented')
|
||||
throw new Error('"remove" not implemented.')
|
||||
},
|
||||
clear: async () => {
|
||||
throw new Error('"clear" not implemented')
|
||||
throw new Error('"clear" not implemented.')
|
||||
},
|
||||
watch: () => {
|
||||
throw new Error('"watch" not implemented')
|
||||
throw new Error('"watch" not implemented.')
|
||||
},
|
||||
unwatch: () => {
|
||||
throw new Error('"unwatch" not implemented')
|
||||
throw new Error('"unwatch" not implemented.')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
100
src/impl/PeerClient.ts
Normal file
100
src/impl/PeerClient.ts
Normal file
|
@ -0,0 +1,100 @@
|
|||
import Peer, { type DataConnection } from 'peerjs'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { PeerClientExtern } from '../domain/externs/PeerClient'
|
||||
|
||||
class PeerClient {
|
||||
private peer: Peer | undefined
|
||||
private connection: DataConnection | undefined
|
||||
|
||||
async connect(id: string) {
|
||||
const connect = (id: string) => {
|
||||
this.peer = new Peer(nanoid())
|
||||
this.peer.on('connection', (e) => {
|
||||
console.log('connection2', e)
|
||||
})
|
||||
const connection = this.peer.connect(id)
|
||||
connection.on('open', () => {
|
||||
console.log('connection open')
|
||||
|
||||
this.connection = connection
|
||||
})
|
||||
connection.on('error', (error) => {
|
||||
console.log('error', error)
|
||||
})
|
||||
}
|
||||
|
||||
this.peer = new Peer(id)
|
||||
this.peer.on('connection', (e) => {
|
||||
console.log('connection1', e)
|
||||
})
|
||||
this.peer.on('open', (e) => {
|
||||
console.log('open', e)
|
||||
this.peer!.on('connection', (e) => {
|
||||
console.log('connection1', e)
|
||||
})
|
||||
})
|
||||
this.peer.on('error', (error) => {
|
||||
if (error.type === 'unavailable-id') {
|
||||
console.log('unavailable-id')
|
||||
|
||||
connect(id)
|
||||
}
|
||||
})
|
||||
|
||||
// return await new Promise((resolve, reject) => {
|
||||
// try {
|
||||
// this.peer = new Peer(id)
|
||||
// this.peer.on('connection', (e) => {
|
||||
// console.log('connection1', e)
|
||||
// })
|
||||
// this.peer
|
||||
// .once('open', (e) => {
|
||||
// resolve(e)
|
||||
// })
|
||||
// .once('error', (error) => {
|
||||
// if (error.type === 'unavailable-id') {
|
||||
// const connection = this.peer!.connect(id)!
|
||||
// connection
|
||||
// .once('open', () => {
|
||||
// console.log('open')
|
||||
// console.log('connection', connection)
|
||||
// this.connection = connection
|
||||
// resolve(id)
|
||||
// })
|
||||
// .once('error', (error) => {
|
||||
// reject(error)
|
||||
// })
|
||||
// } else {
|
||||
// debugger
|
||||
// reject(error)
|
||||
// }
|
||||
// })
|
||||
// } catch (error) {
|
||||
// reject(error)
|
||||
// }
|
||||
// })
|
||||
}
|
||||
|
||||
async sendMessage(message: string) {
|
||||
return await new Promise<void>((resolve, reject) => {
|
||||
if (this.connection) {
|
||||
this.connection.send(message)
|
||||
resolve(undefined)
|
||||
} else {
|
||||
reject(new Error('Connection not established.'))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMessage(callback: (message: string) => void) {
|
||||
this.connection?.on('data', (data: any) => {
|
||||
// callback(data)
|
||||
})
|
||||
}
|
||||
|
||||
close() {
|
||||
this.connection?.close()
|
||||
}
|
||||
}
|
||||
|
||||
export const PeerClientImpl = PeerClientExtern.impl(new PeerClient())
|
|
@ -1,7 +1,8 @@
|
|||
import { createStorage } from 'unstorage'
|
||||
import indexedDbDriver from 'unstorage/drivers/indexedb'
|
||||
import { webExtensionDriver, createStorage } from 'wxt/storage'
|
||||
import { IndexDBStorageExtern, BrowserSyncStorageExtern } from '@/domain/externs/Storage'
|
||||
import { STORAGE_NAME } from '@/constants'
|
||||
import { webExtensionDriver } from '@/utils/webExtensionDriver'
|
||||
|
||||
const indexDBStorage = createStorage({
|
||||
driver: indexedDbDriver({ base: `${STORAGE_NAME}:` })
|
||||
|
|
18
src/utils/callbackToObservable.ts
Normal file
18
src/utils/callbackToObservable.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { Observable } from 'rxjs'
|
||||
|
||||
export type Subscribe<T> = (callback: (event: T) => void) => void
|
||||
|
||||
const callbackToObservable = <T>(subscribe: Subscribe<T>, unsubscribe?: () => void) => {
|
||||
return new Observable((subscriber) => {
|
||||
subscribe((event: T) => {
|
||||
subscriber.next(event)
|
||||
})
|
||||
|
||||
return () => {
|
||||
unsubscribe?.()
|
||||
subscriber.complete()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default callbackToObservable
|
|
@ -7,4 +7,5 @@ export { default as chunk } from './chunk'
|
|||
export { default as compressImage } from './compressImage'
|
||||
export { default as isNullish } from './isNullish'
|
||||
export { default as checkSystemDarkMode } from './checkSystemDarkMode'
|
||||
export { default as storageToObservable } from './storageToObservable'
|
||||
export { default as callbackToObservable } from './callbackToObservable'
|
||||
export { default as stringToHex } from './stringToHex'
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
import { Observable } from 'rxjs'
|
||||
import { type Storage } from '@/domain/externs/Storage'
|
||||
|
||||
const storageToObservable = (storage: Storage) => {
|
||||
return new Observable((subscriber) => {
|
||||
storage.watch((event) => {
|
||||
subscriber.next(event)
|
||||
})
|
||||
return () => {
|
||||
storage.unwatch()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default storageToObservable
|
5
src/utils/stringToHex.ts
Normal file
5
src/utils/stringToHex.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
const stringToHex = (string: string) => {
|
||||
return [...string].map((char) => char.charCodeAt(0).toString(16)).join('')
|
||||
}
|
||||
|
||||
export default stringToHex
|
82
src/utils/webExtensionDriver.ts
Normal file
82
src/utils/webExtensionDriver.ts
Normal file
|
@ -0,0 +1,82 @@
|
|||
import { type Driver, type WatchCallback, defineDriver } from 'unstorage'
|
||||
import browser, { type Storage as BrowserStorage } from 'webextension-polyfill'
|
||||
|
||||
export interface WebExtensionDriverOptions {
|
||||
storageArea: 'sync' | 'local' | 'managed' | 'session'
|
||||
}
|
||||
|
||||
export const webExtensionDriver: (opts: WebExtensionDriverOptions) => Driver = defineDriver((opts) => {
|
||||
const checkPermission = () => {
|
||||
if (browser.storage == null) throw Error("You must request the 'storage' permission to use webExtensionDriver")
|
||||
}
|
||||
|
||||
const _storageListener: (changes: BrowserStorage.StorageAreaSyncOnChangedChangesType) => void = (changes) => {
|
||||
Object.entries(changes).forEach(([key, { newValue }]) => {
|
||||
_listeners.forEach((callback) => {
|
||||
callback(newValue ? 'update' : 'remove', key)
|
||||
})
|
||||
})
|
||||
}
|
||||
const _listeners = new Set<WatchCallback>()
|
||||
|
||||
return {
|
||||
name: 'web-extension:' + opts.storageArea,
|
||||
async hasItem(key) {
|
||||
checkPermission()
|
||||
const res = await browser.storage[opts.storageArea].get(key)
|
||||
return res[key] != null
|
||||
},
|
||||
async getItem(key) {
|
||||
checkPermission()
|
||||
const res = await browser.storage[opts.storageArea].get(key)
|
||||
return res[key] ?? null
|
||||
},
|
||||
async getItems(items) {
|
||||
checkPermission()
|
||||
const res = await browser.storage[opts.storageArea].get(items.map((item) => item.key))
|
||||
return items.map((item) => ({
|
||||
key: item.key,
|
||||
value: res[item.key] ?? null
|
||||
}))
|
||||
},
|
||||
async setItem(key, value) {
|
||||
checkPermission()
|
||||
await browser.storage[opts.storageArea].set({ [key]: value ?? null })
|
||||
},
|
||||
async setItems(items) {
|
||||
checkPermission()
|
||||
const map = items.reduce<Record<string, any>>((map, item) => {
|
||||
map[item.key] = item.value ?? null
|
||||
return map
|
||||
}, {})
|
||||
await browser.storage[opts.storageArea].set(map)
|
||||
},
|
||||
async removeItem(key) {
|
||||
checkPermission()
|
||||
await browser.storage[opts.storageArea].remove(key)
|
||||
},
|
||||
async getKeys() {
|
||||
checkPermission()
|
||||
const all = await browser.storage[opts.storageArea].get()
|
||||
return Object.keys(all)
|
||||
},
|
||||
async clear() {
|
||||
checkPermission()
|
||||
await browser.storage[opts.storageArea].clear()
|
||||
},
|
||||
watch(callback) {
|
||||
checkPermission()
|
||||
_listeners.add(callback)
|
||||
if (_listeners.size === 1) {
|
||||
browser.storage[opts.storageArea].onChanged.addListener(_storageListener)
|
||||
}
|
||||
|
||||
return () => {
|
||||
_listeners.delete(callback)
|
||||
if (_listeners.size === 0) {
|
||||
browser.storage[opts.storageArea].onChanged.removeListener(_storageListener)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
Loading…
Reference in a new issue