Commit 307cb86f authored by baotlake's avatar baotlake Committed by Domi

feat: context menu

parent 0cc896d9
......@@ -20,6 +20,14 @@ export declare global {
platform: string
}
}
interface PromiseConstructor {
withResolvers: () => {
promise: Promise<any>
resolve: (value: any) => void
reject: (reason: any) => void
}
}
}
export {}
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -61,6 +61,8 @@
--fg-hsl: var(--fg-hue) var(--fg-s) var(--fg-l);
--section-gap: 160px;
color-scheme: light;
}
@media (prefers-color-scheme: dark) {
......@@ -88,6 +90,8 @@
--fg-hue: 200;
--fg-s: 0%;
--fg-l: 100%;
color-scheme: dark;
}
input,
......@@ -105,13 +109,26 @@
}
body,
#app {
#app,
:root {
color: var(--color-text);
background: var(--color-background);
transition: color 0.5s, background-color 0.5s;
/* transition:
color 0.5s,
background-color 0.5s; */
line-height: 1.6;
font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue",
font-family:
Inter,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
Oxygen,
Ubuntu,
Cantarell,
"Fira Sans",
"Droid Sans",
"Helvetica Neue",
sans-serif;
font-size: 15px;
text-rendering: optimizeLegibility;
......
......@@ -12,8 +12,8 @@ import {
} from "./offscreen"
import {
registerContentSidebar,
unregisterContentSidebar,
handleContentMounted,
getContentSidebarItem,
} from "./sidebar"
import config from "@/assets/config.json"
import { allFrameScript, contentMainScript } from "@/manifest"
......@@ -39,13 +39,29 @@ chrome.commands.onCommand.addListener(handleCommand)
chrome.runtime.onStartup.addListener(() => {
updateConfig()
})
chrome.runtime.onInstalled.addListener(handleOnInstalled)
chrome.contextMenus.onClicked.addListener(handleContextMenusClicked)
if (isEdge) {
if (isEdge && chrome.sidePanel) {
chrome.sidePanel.setOptions({
enabled: false,
})
}
async function handleOnInstalled(details: chrome.runtime.InstalledDetails) {
chrome.contextMenus.create({
contexts: ["action"],
id: "toggle-sidebar",
title: "Toggle Sidebar",
})
chrome.contextMenus.create({
contexts: ["link"],
id: "open-in-sidebar",
title: "Open Link in Sidebar",
})
}
async function openPipBackground(url: string) {
const tab = await chrome.tabs.create({
url: url,
......@@ -101,16 +117,16 @@ async function handleInvokeRequest(
sender: chrome.runtime.MessageSender
) {
currentSender = sender
let result = await messageInvoke.handleReqMsg(message)
let res = await messageInvoke.handleReqMsg(message)
if (!sender.tab?.id) {
console.error("sender tab id is undefined", sender)
}
if (result) {
if (res) {
chrome.tabs.sendMessage(sender.tab?.id!, {
type: MessageType.invokeResponse,
...result,
...res,
})
}
}
......@@ -134,24 +150,22 @@ function handleMessage(message: any, sender: chrome.runtime.MessageSender) {
chrome.tabs.sendMessage(message.tabId, message.message)
break
case MessageType.invokeRequest:
handleInvokeRequest(message, sender)
currentSender = sender
messageInvoke.handleReqMsg(message, sender)
break
case MessageType.invokeResponse:
messageInvoke.handleResMsg(message)
break
case MessageType.contentMounted:
handleContentMounted(sender.tab!.id!)
handleContentMounted(sender)
break
case MessageType.registerContentSidebar:
registerContentSidebar({ tabId: sender.tab!.id!, url: message.url })
break
case MessageType.unregisterContentSidebar:
unregisterContentSidebar(sender.tab!.id!)
registerContentSidebar(sender.tab!.id!, message.info)
break
}
}
async function handleToggleMinimize() {
async function toggleMinimize() {
const { pipWindow } = await chrome.storage.local.get({ pipWindow: null })
if (!pipWindow) return
const windowInfo = await chrome.windows.get(pipWindow.windowId)
......@@ -160,17 +174,65 @@ async function handleToggleMinimize() {
const tabId = pipWindow.tabId
messageInvoke.invoke({
tabId,
func: ServiceFunc.toggleMinimize,
args: [],
tabId,
})
}
async function toggleSidebar(tab?: chrome.tabs.Tab) {
if (tab?.windowId) {
chrome.sidePanel.open({
windowId: tab?.windowId,
})
}
messageInvoke.invoke({
func: ServiceFunc.toggleSidebar,
args: [],
})
}
async function openInSidebar(url: string, tab?: chrome.tabs.Tab) {
let contentMode = isEdge || !chrome.sidePanel
const contentSidebar = getContentSidebarItem(tab?.id!)
if (contentSidebar && contentSidebar.visible) {
contentMode = true
}
if (contentMode) {
await messageInvoke.invoke({
func: ServiceFunc.toggleContentSidebar,
args: [{ visible: true, collapse: false }],
tabId: tab?.id,
})
} else {
await chrome.sidePanel.open({
windowId: tab?.windowId!,
})
}
await messageInvoke.invoke({
key: ServiceFunc.waitSidebar,
func: ServiceFunc.waitSidebar,
args: [],
timeout: 300,
})
await messageInvoke.invoke({
func: ServiceFunc.openInSidebar,
args: [{ urls: [url] }],
})
}
function handleCommand(command: string) {
function handleCommand(command: string, tab?: chrome.tabs.Tab) {
console.log("command: ", command)
switch (command) {
case "toggleMinimize":
handleToggleMinimize()
toggleMinimize()
break
case "toggleSidebar":
toggleSidebar(tab)
break
}
}
......@@ -234,3 +296,18 @@ async function updateConfig() {
loadCandidates,
})
}
async function handleContextMenusClicked(
info: chrome.contextMenus.OnClickData,
tab?: chrome.tabs.Tab
) {
console.log(info)
switch (info.menuItemId) {
case "toggle-sidebar":
toggleSidebar(tab)
break
case "open-in-sidebar":
openInSidebar(info.linkUrl!, tab)
break
}
}
import { MessageType } from "@/types"
import { messageInvoke } from "@/utils/invoke"
import { ServiceFunc } from "@/types"
import { openSidebar } from "@/utils/ext"
type ContentSidebar = {
type ContentSidebarItem = {
visible: boolean
tabId: number
url?: string
urls: string[]
}
const contentSidebarMap = new Map<number, ContentSidebar>()
const contentSidebarMap = new Map<number, ContentSidebarItem>()
export function registerContentSidebar({ tabId, url }: ContentSidebar) {
contentSidebarMap.set(tabId, { tabId, url })
export function registerContentSidebar(
tabId: number,
info: Partial<ContentSidebarItem>
) {
const item = contentSidebarMap.get(tabId) || { visible: false, urls: [] }
contentSidebarMap.set(tabId, {
...item,
...info,
tabId,
})
}
export function unregisterContentSidebar(tabId: number) {
contentSidebarMap.delete(tabId)
}
export async function handleContentMounted(
sender: chrome.runtime.MessageSender
) {
if (sender.frameId !== 0) return
if (!sender.tab) return
const tabId = sender.tab.id!
export async function handleContentMounted(tabId: number) {
const sidebar = contentSidebarMap.get(tabId)
if (sidebar) {
await chrome.storage.session.set({
sidebarInitUrl: { content: sidebar.url },
})
chrome.tabs.sendMessage(tabId, {
type: MessageType.openContentSidebar,
url: sidebar.url,
const item = contentSidebarMap.get(tabId)
console.log("handleContentMounted", item)
if (item && item.visible) {
openSidebar({
urls: item.urls,
tab: sender.tab,
sidePanel: false,
})
}
}
export function getContentSidebarItem(tabId: number) {
return contentSidebarMap.get(tabId)
}
......@@ -109,6 +109,9 @@ onMounted(() => {
window.addEventListener("message", handleMessage)
chrome.runtime.sendMessage({
type: MessageType.registerContentSidebar,
info: {
visible: true,
},
})
})
......@@ -118,8 +121,12 @@ onUnmounted(() => {
window.removeEventListener("keyup", handleKeyUp)
window.removeEventListener("message", handleMessage)
chrome.runtime.sendMessage({
type: MessageType.unregisterContentSidebar,
type: MessageType.registerContentSidebar,
info: {
visible: false,
},
})
console.log("content sidebar unmounted")
})
</script>
......
<script setup lang="ts">
import { ref } from "vue"
import { ref, onMounted } from "vue"
import ContentSidebar from "./ContentSidebar.vue"
import { sidebarAddon } from "@/store/content"
import { autoPointerCapture } from "@/utils/dom"
import { messageInvoke } from "@/utils/invoke"
import { ServiceFunc } from "@/types"
const logoUrl = chrome.runtime.getURL("/logo.svg")
const top = ref(72)
......@@ -13,18 +15,37 @@ function handlePointerMove(e: PointerEvent) {
top.value = Math.max(12, Math.min(window.innerHeight - 60, value))
}
}
onMounted(() => {
messageInvoke.register(
ServiceFunc.toggleContentSidebar,
async ({ visible, collapse }: { visible: boolean; collapse?: boolean }) => {
sidebarAddon.visible = visible ?? !sidebarAddon.visible
sidebarAddon.collapse = collapse ?? sidebarAddon.collapse
// how to
awai messageInvoke.invoke({
key: ServiceFunc.waitSidebar,
func: ServiceFunc.waitSidebar,
args: [],
timeout: 300,
})
}
)
})
</script>
<template>
<ContentSidebar
v-if="sidebarAddon.visible"
:hidden="sidebarAddon.hidden"
:hidden="sidebarAddon.collapse"
@close="sidebarAddon.visible = false"
@hide="sidebarAddon.hidden = true"
@hide="sidebarAddon.collapse = true"
/>
<div
v-if="sidebarAddon.hidden"
v-if="sidebarAddon.collapse"
:class="[
'fixed top-16 right-0 p-1.5 bg-background/65 backdrop-blur-md cursor-pointer',
'rounded-s shadow-lg z-[999999] hover:bg-background-mute/65',
......@@ -32,7 +53,7 @@ function handlePointerMove(e: PointerEvent) {
:style="{ top: top + 'px' }"
@pointerdown="autoPointerCapture"
@pointermove="handlePointerMove"
@click="sidebarAddon.hidden = false"
@click="sidebarAddon.collapse = false"
>
<img :src="logoUrl" class="size-4 pointer-events-none select-none" />
</div>
......
......@@ -81,7 +81,7 @@ const handleDrop = (e: DragEvent) => {
<template>
<div
:class="[
'flex gap-1 items-center justify-between h-9 px-1 z-10 shadow-sm',
'flex gap-1 items-center justify-between h-9 px-1 z-10 shadow-sm shrink-0',
'*:size-7 *:flex *:items-center *:justify-center ',
]"
@dragenter="handleDragEnter"
......
......@@ -29,7 +29,7 @@ const emit = defineEmits({
<Search @go="(url) => emit('go', url)" />
</div>
<div class="flex flex-wrap gap-x-4 gap-y-4 justify-center">
<div class="flex flex-wrap gap-x-3 gap-y-4 justify-center">
<SiteButton
v-for="item of recentItems.slice(0, 12)"
:key="item.url"
......@@ -44,7 +44,7 @@ const emit = defineEmits({
<!-- <div class="text-center my-3">Popular</div> -->
<div class="w-full my-6 border-b border-background-soft h-0"></div>
<div class="flex flex-wrap gap-x-4 gap-y-4 justify-center">
<div class="flex flex-wrap gap-x-3 gap-y-4 justify-center">
<SiteButton
v-for="item of popularItems"
:icon="item.icon"
......
......@@ -12,7 +12,9 @@ const { t } = useI18n()
const topFrame = window.parent == window
onMounted(() => {
if (topFrame) {
chrome.runtime?.sendMessage({ type: MessageType.contentMounted })
}
})
</script>
......
......@@ -46,16 +46,15 @@ function handleMessage(
})
sendResponse({ type: MessageType.contentHere })
break
case MessageType.invokeResponse:
messageInvoke.handleResMsg(message)
break
case MessageType.showChatDocs:
chatDocsPanel.visible = true
break
case MessageType.openContentSidebar:
sidebarAddon.visible = true
sidebarAddon.hidden = false
sidebarAddon.url = message.url
// case MessageType.openContentSidebar:
// sidebarAddon.visible = true
// sidebarAddon.collapse = false
// break
case MessageType.invokeResponse:
messageInvoke.handleResMsg(message)
break
case MessageType.invokeRequest:
messageInvoke.handleReqMsg(message).then((result) => {
......@@ -172,12 +171,12 @@ function handleFrameMessage(e: MessageEvent) {
detail: { url: e.data.url },
})
case FrameMessageType.invokeRequest:
handleInvokeRequest(e.data, e.source as Window)
handleWebviewInvokeRequest(e.data, e.source as Window)
break
}
}
async function handleInvokeRequest(message: any, source: Window) {
async function handleWebviewInvokeRequest(message: any, source: Window) {
const { key, func, args } = message
let result = null
let error = null
......
......@@ -77,6 +77,7 @@ const manifest = {
"sidePanel",
"declarativeNetRequestWithHostAccess",
"declarativeNetRequestFeedback",
"contextMenus",
// "cookies",
],
optional_permissions: [],
......@@ -90,6 +91,12 @@ const manifest = {
description: "__MSG_toggle_minimize_desc__",
global: true,
},
toggleSidebar: {
suggested_key: {
default: "Ctrl+0",
},
description: "Toggle Sidebar",
},
},
web_accessible_resources: [
{
......@@ -100,9 +107,9 @@ const manifest = {
],
content_security_policy: {
extension_pages: __DEV__
? `script-src 'self' http://localhost:3000 'wasm-unsafe-eval';`
? `script-src 'self' http://localhost:5173 'wasm-unsafe-eval';`
: `script-src 'self' 'wasm-unsafe-eval'`,
},
} satisfies Manifest as unknown as chrome.runtime.Manifest
} satisfies Manifest as any
export default manifest
......@@ -20,7 +20,7 @@ import IconArrowCircleRight from "@/components/icons/IconArrowCircleRight.vue"
import IconSplitscreenRight from "@/components/icons/IconSplitscreenRight.vue"
import IconLogo from "@/components/icons/IconLogo.vue"
import SiteButton from "@/components/SiteButton.vue"
import { getIsEdge } from "@/utils/ext"
import { getIsEdge, openSidebar } from "@/utils/ext"
import { handleImgError } from "@/utils/dom"
import { homeUrl, feedbackUrl } from "@/utils/const"
......@@ -113,7 +113,9 @@ function handleKeydown(e: KeyboardEvent) {
}
if (e.code == "Backslash" && (e.ctrlKey || e.metaKey)) {
e.preventDefault()
openSidebar()
openSidebar({
urls: [],
}).then(() => close())
}
if (e.code == "ControlLeft" || e.code == "ControlRight") {
......@@ -158,30 +160,7 @@ async function handlePipPopup() {
window.close()
}
async function openSidebar(url = "") {
chrome.runtime.sendMessage({
type: MessageType.openInSidebar,
tabId: -1,
url,
})
const win = await chrome.windows.getCurrent()
await chrome.storage.session.set({
sidebarInitUrl: { sidepanel: url },
})
await chrome.sidePanel.open({ windowId: win.id! })
window.close()
}
async function openContentSidebar(url = "") {
await chrome.storage.session.set({
sidebarInitUrl: { content: url },
})
await chrome.tabs.sendMessage(activeTab.value.id!, {
type: MessageType.openContentSidebar,
tabId: activeTab.value.id,
url,
})
function close() {
window.close()
}
......@@ -308,17 +287,18 @@ function showChatDocs() {
:disabled="isEdge && !avaiable"
:title="t('openInSidebar')"
@click="
() => {
if (isEdge || keyboard.ctrl) {
return openContentSidebar()
}
openSidebar(activeTab.url)
(e) => {
openSidebar({
urls: [activeTab.url!],
sidePanel: !(isEdge || e.ctrlKey || e.metaKey),
tab: activeTab,
}).then(() => close())
}
"
>
<IconSplitscreenRight class="size-8 shrink-0 scale-95" />
<div class="flex items-center justify-between flex-1 w-2/3 gap-2">
<span class="text-sm font-bold leading-4 truncate">
<span class="text-base font-bold leading-4 truncate">
{{ isEdge ? t("openSidebar") : t("openInSidebar") }}
</span>
<span class="text-xs">CTRL + \</span>
......@@ -372,10 +352,10 @@ function showChatDocs() {
return handleClickLaunch(item.url)
}
if (isEdge || keyboard.ctrl) {
return openContentSidebar(item.url)
}
openSidebar(item.url)
openSidebar({
urls: [item.url],
sidePanel: !(isEdge || keyboard.ctrl),
}).then(() => close())
}
"
/>
......
......@@ -10,7 +10,8 @@ import {
import config from "@/assets/config.json"
import Webview, { type PageInfo } from "@/components/Webview.vue"
import { useI18n } from "@/utils/i18n"
import { MessageType } from "@/types"
import { messageInvoke } from "@/utils/invoke"
import { MessageType, ServiceFunc } from "@/types"
import SidebarHome from "@/components/sidebar/SidebarHome.vue"
import Navbar from "@/components/sidebar/Navbar.vue"
import { FrameMessageType } from "@/types"
......@@ -121,37 +122,33 @@ onMounted(() => {
}
)
getSession({
sidebarInitUrl: {} as Record<string, string>,
}).then(({ sidebarInitUrl }) => {
console.log("[sidebar]", sidebarInitUrl, mode.value)
const url = sidebarInitUrl?.[mode.value]
if (url && !isProtectedUrl(url)) {
go(url)
}
})
chrome.tabs.getCurrent().then((t) => {
currentTab.tabId = t?.id || -1
})
chrome.runtime.onMessage.addListener(handleMessage)
})
onUnmounted(() => {
chrome.runtime.onMessage.removeListener(handleMessage)
})
async function handleMessage(message: any) {
switch (message.type) {
case MessageType.openInSidebar:
const url = message.url
if (message.tabId == currentTab.tabId && !isProtectedUrl(url)) {
go(url)
const mountedAt = Date.now()
messageInvoke
.register(ServiceFunc.waitSidebar, () => {})
.register(ServiceFunc.toggleSidebar, () => {
if (Date.now() - mountedAt > 200) {
window.close()
}
break
})
.register(ServiceFunc.openInSidebar, ({ urls }: { urls: string[] }) => {
urls.forEach((url) => {
if (!isProtectedUrl(url)) {
go(url)
}
}
})
})
chrome.runtime.sendMessage({
type: MessageType.invokeResponse,
key: ServiceFunc.waitSidebar,
success: true,
value: null,
})
})
function handlePageLoad(i: number) {
pagesInfo[i] = { ...pagesInfo[i]!, loading: true }
......@@ -163,7 +160,9 @@ async function handlePageLoaded(i: number, pageInfo: PageInfo) {
if (mode.value === "content") {
chrome.runtime.sendMessage({
type: MessageType.registerContentSidebar,
pages: pages,
info: {
urls: pages.map((p) => p.url),
},
})
}
......@@ -328,7 +327,12 @@ function handlePointerLeave() {
@drop="go"
/>
<div :class="['w-full h-full', { hidden: active != -1 }]">
<div
:class="[
'scrollbar w-full h-full flex-1 overflow-auto ',
{ hidden: active != -1 },
]"
>
<SidebarHome
:recentItems="recentItems"
:popularItems="popularItems"
......
......@@ -16,5 +16,8 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
case MessageType.invokeResponse:
messageInvoke.handleResMsg(message)
break
case MessageType.invokeRequest:
messageInvoke.handleReqMsg(message, sender)
break
}
})
......@@ -2,9 +2,23 @@ import "@/assets/main.css"
import { createApp } from "vue"
import { i18n } from "@/utils/i18n"
import { messageInvoke } from "@/utils/invoke"
import Sidebar from "./Sidebar.vue"
import { MessageType } from "@/types"
const app = createApp(Sidebar)
app.use(i18n)
app.mount("#app")
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
console.log("[sidebar] ", message.type, message, sender)
switch (message.type) {
case MessageType.invokeResponse:
messageInvoke.handleResMsg(message)
break
case MessageType.invokeRequest:
messageInvoke.handleReqMsg(message, sender)
break
}
})
......@@ -23,8 +23,7 @@ export const docsAddon = reactive({
export const sidebarAddon = reactive({
visible: false,
hidden: false,
url: "",
collapse: false,
})
type DocInputItem = {
......
declare namespace chrome.declarativeNetRequest {
export function getSessionRules(filter: {
ruleIds: number[]
}): Promise<Rule[]>
}): Promise<chrome.declarativeNetRequest.Rule[]>
}
declare interface ManifestPatch {
......
......@@ -6,20 +6,14 @@ export enum MessageType {
bgPipLaunch = "bg-pip-launch",
pipLaunch = "pip-launch",
contentMounted = "content-mount",
// getPipWinInfo = "get-pip-win-info",
// pipWinInfo = "pip-win-info",
updateWindow = "update-window",
removeWindow = "remove-window",
forwardToTab = "forward-to-tab",
// toOffscreen = "to-offscreen",
// fromOffscreen = "from-offscreen",
invokeRequest = "invoke-request",
invokeResponse = "invoke-Response",
showChatDocs = "show-chat-docs",
openInSidebar = "open-in-sidebar",
registerContentSidebar = "register-content-sidebar",
unregisterContentSidebar = "unregister-content-sidebar",
openContentSidebar = "open-content-sidebar",
}
export enum ServiceFunc {
......@@ -33,6 +27,10 @@ export enum ServiceFunc {
getPipWindow = "get-pip-window",
getMyTab = "get-my-tab",
waitOffscreen = "wait-offscreen",
toggleSidebar = "toggle-sidebar",
toggleContentSidebar = "toggle-content-sidebar",
waitSidebar = "wait-sidebar",
openInSidebar = "open-in-sidebar",
}
export type ParseDocOptions = {
......
import { contentMainScript } from "@/manifest"
import { MessageType } from "@/types"
import { MessageType, ServiceFunc } from "@/types"
import { messageInvoke } from "./invoke"
type MessageSender = chrome.runtime.MessageSender
......@@ -346,3 +347,60 @@ export async function getPipWindow({
const win = windows.find((w) => w.width === width && w.height === height)
return win
}
export async function openSidebar({
urls,
tab,
sidePanel,
}: {
urls: string[]
tab?: chrome.tabs.Tab
sidePanel?: boolean
}) {
sidePanel = sidePanel ?? !!chrome.sidePanel
if (sidePanel) {
let windowId = tab?.windowId
if (!windowId) {
const win = await chrome.windows.getCurrent()
windowId = win.id
}
await chrome.sidePanel.open({
windowId: windowId!,
})
await messageInvoke.invoke({
key: ServiceFunc.waitSidebar,
func: ServiceFunc.waitSidebar,
args: [],
timeout: 300,
})
messageInvoke.invoke({
func: ServiceFunc.openInSidebar,
args: [{ urls }],
})
return
}
let tabId = tab?.id
if (!tabId) {
const tabs = await chrome.tabs.query({
active: true,
currentWindow: true,
})
tabId = tabs[0].id
}
await messageInvoke.invoke({
func: ServiceFunc.toggleContentSidebar,
args: [{ visible: true, collapse: false }],
tabId: tabId,
})
messageInvoke.invoke({
func: ServiceFunc.openInSidebar,
args: [{ urls }],
tabId: tabId,
})
}
type CallbackItem = {
resolve: (v: any) => void
reject: (e: any) => void
}
export interface InvokeReq {
func: string
args: any[]
......@@ -17,8 +12,10 @@ export interface InvokeRes {
value: any
}
type PromiseObject = ReturnType<PromiseConstructor["withResolvers"]>
export abstract class Invoke {
private pendingCallback: Record<string, CallbackItem>
private pendingCallback: Record<string, PromiseObject>
private count: number
private name: string
private uniqueId: string
......@@ -39,28 +36,29 @@ export abstract class Invoke {
protected getReturnValue(key: string, req: InvokeReq) {
let timer: any
const { func, timeout } = req
const promise = new Promise<any>((resolve, reject) => {
this.pendingCallback[key] = { resolve, reject }
const p = this.pendingCallback[key] || Promise.withResolvers()
this.pendingCallback[key] = p
if (timeout) {
timer = setTimeout(
() => reject(`"${this.name}" invoke timeout: ${func} key: ${key}`),
() => p.reject(`"${this.name}" invoke timeout: ${func} key: ${key}`),
timeout
)
}
})
promise.finally(() => {
p.promise.finally(() => {
delete this.pendingCallback[key]
timer && clearTimeout(timer)
})
return promise
return p.promise
}
protected setReturnValue(key: string, success: boolean, value: any) {
const callback = this.pendingCallback[key]
if (callback) {
const fn = success != false ? callback.resolve : callback.reject
const p = this.pendingCallback[key]
if (p) {
const fn = success != false ? p.resolve : p.reject
fn(value)
} else {
console.error(`unknown invoke callback message: ${key}`)
......@@ -69,12 +67,21 @@ export abstract class Invoke {
}
abstract send(req: InvokeReq): Promise<{ key: string }>
public sendRes?(res: InvokeRes, sender?: any): void
public handleResMsg(message: InvokeRes) {
const { key, success, value } = message
if (!key || typeof success !== "boolean") {
console.error(`invalid invoke response: ${key}`, message)
}
this.setReturnValue(key, success, value)
}
public async handleReqMsg(message: InvokeReq) {
public async handleReqMsg(
message: InvokeReq & { key: string },
sender?: any
) {
const { key, func, args, timeout } = message
let result = null
let error = null
......@@ -92,7 +99,9 @@ export abstract class Invoke {
error = err
}
return { key, success: !error, value: !error ? result : error }
const res = { key, success: !error, value: !error ? result : error }
this.sendRes?.(res, sender)
return res
}
public async invoke<T = any>(req: InvokeReq): Promise<T> {
......
import { MessageType, ServiceFunc, type ParseDocOptions } from "@/types"
import Invoke, { type InvokeReq } from "./Invoke"
import Invoke, { type InvokeReq, type InvokeRes } from "./Invoke"
class MessageInvoke extends Invoke {
public async send(req: InvokeReq & { tabId?: number }) {
......@@ -22,10 +22,21 @@ class MessageInvoke extends Invoke {
return { key }
}
public handleResMsg(message: any) {
if (message?.type === MessageType.invokeResponse) {
const { key, success, value } = message
this.setReturnValue(key, success, value)
public sendRes(res: InvokeRes, sender?: chrome.runtime.MessageSender) {
if (!sender) {
return
}
if (sender.tab?.id) {
chrome.tabs.sendMessage(sender.tab.id, {
type: MessageType.invokeResponse,
...res,
})
} else {
chrome.runtime.sendMessage({
type: MessageType.invokeResponse,
...res,
})
}
}
......
......@@ -3,6 +3,7 @@
"include": [
"utils",
"./src/manifest.ts",
"./src/types/**/*.ts",
"package.json",
"vite.config.*",
"vitest.config.*",
......@@ -14,6 +15,9 @@
"composite": true,
"module": "ESNext",
"moduleResolution": "Bundler",
"types": ["node"]
"types": [
"node",
"@types/chrome"
]
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment