Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
b77d410
Initial plan
Copilot May 7, 2026
43d5dc9
feat: improve hub communication via background_script
Copilot May 7, 2026
27e7907
fix: address code review feedback on useCommandHubBridge and backgrou…
Copilot May 7, 2026
5216e19
fix: add error handling for async calls in HubBridge and background_s…
Copilot May 7, 2026
9763e21
feat: use externally_connectable and onMessageExternal for hub messaging
Copilot May 7, 2026
161005d
fix: add error handling for onMessageExternal IPC calls and removeLis…
Copilot May 7, 2026
1d355bb
Merge branch 'main' into copilot/improve-communication-with-selection…
Copilot May 8, 2026
9779896
Fix: Enable adding and deleting commands to work correctly.
ujiro99 May 9, 2026
6c34838
Update: Refactoring.
ujiro99 May 9, 2026
773351d
Update: Enable sharing commands via port.postMessage.
ujiro99 May 9, 2026
6245089
Update: Separate HUB-related logic into a different file.
ujiro99 May 9, 2026
307b775
fix: use let tabId to avoid ReferenceError in onPortConnect and fix r…
Copilot May 10, 2026
ded9a56
fix: address code review feedback on background.ts and background_scr…
Copilot May 10, 2026
54eafae
fix: address code review - remove non-null assertion and add error ha…
Copilot May 10, 2026
4b39fef
fix: rename install_id to client_id and clarify portConnect vs onPort…
Copilot May 10, 2026
bd6d42f
Fix: Property name.
ujiro99 May 10, 2026
cefad1b
Update: Remove localhost from `externally_connectable.matches`.
ujiro99 May 10, 2026
9a92e95
fix: fix Ipc.callListener type, hubOrigin calculation, and hubShare e…
Copilot May 10, 2026
9b3a15f
test: add SH-08 error case test for shareCommandToHub catch handler
Copilot May 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions packages/extension/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,28 @@
"js": [
"src/command_hub.tsx"
]
},
{
"matches": [
"https://selection-command.com/*",
"https://selection-command-hub.siro-cola.workers.dev/*",
"http://localhost:3000/*"
],
Comment thread
ujiro99 marked this conversation as resolved.
"js": [
"src/new_command_hub.tsx"
]
Comment thread
ujiro99 marked this conversation as resolved.
}
],
"background": {
"service_worker": "src/background_script.ts"
},
"externally_connectable": {
"matches": [
"https://selection-command.com/*",
"https://selection-command-hub.siro-cola.workers.dev/*",
"http://localhost:3000/*"
]
},
Comment thread
ujiro99 marked this conversation as resolved.
"options_page": "src/options_page.html",
"action": {},
"permissions": [
Expand Down
53 changes: 34 additions & 19 deletions packages/extension/src/background_script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@ import { execute } from "@/action/background"
import * as ActionHelper from "@/action/helper"
import type { WindowType } from "@/types"
import { Storage, SESSION_STORAGE_KEY } from "@/services/storage"
import { ANALYTICS_EVENTS, sendEvent } from "@/services/analytics"
import {
ANALYTICS_EVENTS,
sendEvent,
getOrCreateClientId,
} from "@/services/analytics"
import * as HubBackground from "@/services/hub/background"

import { importIf } from "@import-if"
importIf("production", "./lib/sentry/initialize")
Expand Down Expand Up @@ -211,7 +216,7 @@ const commandFuncs = {

if (!cmd) {
console.error("invalid command", param.command)
response(false)
response({ result: false, error: "Invalid command format" })
return true
}

Expand All @@ -227,8 +232,13 @@ const commandFuncs = {
SCREEN.COMMAND_HUB,
)
})
.then(() => {
response(true)
.then(async () => {
const clientId = await getOrCreateClientId()
response({ result: true, install_id: clientId })
})
.catch((err) => {
console.error("[addCommand] Failed:", err)
response({ result: false, error: err?.message ?? "Unknown error" })
})
Comment thread
ujiro99 marked this conversation as resolved.
return true
},
Expand All @@ -241,22 +251,20 @@ const commandFuncs = {
const remove = async () => {
const current = await Storage.getCommands()
const commandToRemove = current.find((c) => c.id === param.id)
if (commandToRemove) {
const newCommands = current.filter((c) => c.id !== param.id)
if (newCommands.length === current.length) {
response(false)
return
}
await Storage.setCommands(newCommands)
await sendEvent(
ANALYTICS_EVENTS.COMMAND_REMOVE,
{
event_label: commandToRemove.openMode,
},
SCREEN.COMMAND_HUB,
)
if (!commandToRemove) {
response({ result: false, error: "Command not found" })
return
}
response(true)
const newCommands = current.filter((c) => c.id !== param.id)
await Storage.setCommands(newCommands)
await sendEvent(
ANALYTICS_EVENTS.COMMAND_REMOVE,
{
event_label: commandToRemove.openMode,
},
SCREEN.COMMAND_HUB,
)
response({ result: true })
}
remove()
return true
Expand Down Expand Up @@ -401,6 +409,11 @@ const commandFuncs = {
[BgCommand.getTabId]: getTabId,
[BgCommand.getActiveTabId]: getActiveTabId,

//
// Hub
//
[BgCommand.shareCommandToHub]: HubBackground.shareCommandToHub,

//
// PageAction
//
Expand All @@ -420,6 +433,8 @@ for (const key in BgCommand) {
Ipc.addListener(command, commandFuncs[key])
}

HubBackground.initHubExternalListener()

const updateWindowSize = async (
commandId: string,
width: number,
Expand Down
3 changes: 0 additions & 3 deletions packages/extension/src/components/commandHub/CommandHub.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import { useCommandHubBridge } from "@/hooks/useCommandHubBridge"

export const CommandHub = (): JSX.Element => {
useCommandHubBridge()
return <></>
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import { PageActionHelp } from "@/components/help/PageActionHelp"
import { CommandType } from "@/components/option/editor/CommandType"
import { SearchUrlAssistButton } from "@/components/option/editor/SearchUrlAssistButton"
import { SearchUrlAssistDialog } from "@/components/option/editor/SearchUrlAssistDialog"
import { hasCommandChanged } from "@/components/option/editor/commandChangedDetector"
import { MenuImage } from "@/components/menu/MenuImage"
import { Tooltip } from "@/components/Tooltip"
import { PageActionStep } from "@/types/schema"
Expand Down Expand Up @@ -93,7 +94,6 @@ import {
commandSchema,
CommandSchemaType,
isPageActionType,
isAiPromptType,
} from "@/types/schema"
import type {
SelectionCommand,
Expand Down Expand Up @@ -468,13 +468,12 @@ const CommandEditDialogInner = ({
if (command.sourceType === COMMAND_SOURCE_TYPE.SELF_CREATED) return
if (getValues("sourceType") === COMMAND_SOURCE_TYPE.SELF_UPDATED) return

const changed =
(isSearchType(command) && searchUrl !== command.searchUrl) ||
(isPageActionType(command) &&
JSON.stringify(pageActionOption) !==
JSON.stringify(command.pageActionOption)) ||
(isAiPromptType(command) &&
aiPromptPrompt !== command.aiPromptOption.prompt)
const changed = hasCommandChanged(
command,
searchUrl,
pageActionOption,
aiPromptPrompt,
)

if (changed) {
setValue("sourceType", COMMAND_SOURCE_TYPE.SELF_UPDATED)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { describe, it, expect } from "vitest"
import { hasCommandChanged } from "./commandChangedDetector"
import { OPEN_MODE, PAGE_ACTION_OPEN_MODE } from "@/const"
import type {
SearchCommand,
PageActionCommand,
AiPromptCommand,
PageActionOption,
} from "@/types"

const makeSearchCommand = (
overrides: Partial<SearchCommand> = {},
): SearchCommand => ({
id: "cmd-1",
title: "Google",
iconUrl: "https://example.com/icon.png",
openMode: OPEN_MODE.POPUP,
searchUrl: "https://example.com/search?q=%s",
...overrides,
})

const makePageActionOption = (
overrides: Partial<PageActionOption> = {},
): PageActionOption => ({
startUrl: "https://example.com",
openMode: PAGE_ACTION_OPEN_MODE.POPUP,
steps: [],
...overrides,
})

const makePageActionCommand = (
overrides: Partial<Omit<PageActionCommand, "pageActionOption">> & {
pageActionOption?: Partial<PageActionOption>
} = {},
): PageActionCommand => {
const { pageActionOption, ...rest } = overrides
return {
id: "cmd-2",
title: "Example Action",
iconUrl: "https://example.com/icon.png",
openMode: OPEN_MODE.PAGE_ACTION,
pageActionOption: makePageActionOption(pageActionOption),
...rest,
}
}

const makeAiPromptCommand = (
overrides: Partial<AiPromptCommand> = {},
): AiPromptCommand => ({
id: "cmd-3",
title: "AI Summarize",
iconUrl: "https://example.com/icon.png",
openMode: OPEN_MODE.AI_PROMPT,
aiPromptOption: {
serviceId: "chatgpt",
prompt: "Summarize: {text}",
openMode: OPEN_MODE.POPUP,
},
...overrides,
})

describe("hasCommandChanged", () => {
describe("SearchCommand", () => {
it("returns false when searchUrl is unchanged", () => {
const cmd = makeSearchCommand()
expect(hasCommandChanged(cmd, cmd.searchUrl!, undefined, "")).toBe(false)
})

it("returns true when searchUrl is changed", () => {
const cmd = makeSearchCommand()
expect(
hasCommandChanged(cmd, "https://bing.com/search?q=%s", undefined, ""),
).toBe(true)
})
})

describe("PageActionCommand", () => {
it("returns false when pageActionOption is unchanged", () => {
const cmd = makePageActionCommand()
expect(hasCommandChanged(cmd, "", { ...cmd.pageActionOption }, "")).toBe(
false,
)
})

it("returns true when startUrl is changed", () => {
const cmd = makePageActionCommand()
expect(
hasCommandChanged(
cmd,
"",
{ ...cmd.pageActionOption, startUrl: "https://other.com" },
"",
),
).toBe(true)
})

it("returns true when pageUrl is changed", () => {
const cmd = makePageActionCommand()
expect(
hasCommandChanged(
cmd,
"",
{ ...cmd.pageActionOption, pageUrl: "https://example.com/*" },
"",
),
).toBe(true)
})

it("returns false when only openMode is changed", () => {
const cmd = makePageActionCommand()
expect(
hasCommandChanged(
cmd,
"",
{ ...cmd.pageActionOption, openMode: PAGE_ACTION_OPEN_MODE.TAB },
"",
),
).toBe(false)
})

it("does not throw when currentPageActionOption is null, returns true (differs from saved)", () => {
const cmd = makePageActionCommand()
expect(hasCommandChanged(cmd, "", null, "")).toBe(true)
})

it("does not throw when currentPageActionOption is undefined, returns true (differs from saved)", () => {
const cmd = makePageActionCommand()
expect(hasCommandChanged(cmd, "", undefined, "")).toBe(true)
})
})

describe("AiPromptCommand", () => {
it("returns false when prompt is unchanged", () => {
const cmd = makeAiPromptCommand()
expect(
hasCommandChanged(cmd, "", undefined, cmd.aiPromptOption.prompt),
).toBe(false)
})

it("returns true when prompt is changed", () => {
const cmd = makeAiPromptCommand()
expect(hasCommandChanged(cmd, "", undefined, "Translate: {text}")).toBe(
true,
)
})
})

describe("other command types", () => {
it("returns false for CopyCommand", () => {
const cmd = makeSearchCommand({ openMode: OPEN_MODE.COPY })
expect(hasCommandChanged(cmd, "", undefined, "")).toBe(false)
})
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { isSearchType, isPageActionType, isAiPromptType } from "@/types/schema"
import type { SelectionCommand, PageActionOption } from "@/types"

/**
* Returns true when the current form values differ from the saved command,
* ignoring pageActionOption.openMode (which represents display preference,
* not the command's core behavior).
*/
export function hasCommandChanged(
command: SelectionCommand,
currentSearchUrl: string,
currentPageActionOption: Partial<PageActionOption> | null | undefined,
currentAiPromptPrompt: string,
): boolean {
if (isSearchType(command)) {
return currentSearchUrl !== command.searchUrl
}
if (isPageActionType(command)) {
const { openMode: _a, ...pao } = currentPageActionOption ?? {}
const { openMode: _b, ...cmdPao } = command.pageActionOption
return JSON.stringify(pao) !== JSON.stringify(cmdPao)
}
if (isAiPromptType(command)) {
return currentAiPromptPrompt !== command.aiPromptOption.prompt
}
return false
}
2 changes: 1 addition & 1 deletion packages/extension/src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ export const HUB_URL = isDebug
: "https://ujiro99.github.io/selection-command"

export const NEW_HUB_URL =
import.meta.env?.VITE_NEW_HUB_URL ?? "https://selection-command-hub.pages.dev"
import.meta.env?.VITE_NEW_HUB_URL ?? "https://selection-command.com"

export const NEW_HUB_SUPPORTED_LOCALES = [
"de",
Expand Down
Loading