Skip to content

Commit 8e4726a

Browse files
committed
fix: implement URL validation and timeout handling for url like chrome webstore
1 parent eda482f commit 8e4726a

File tree

8 files changed

+62
-8
lines changed

8 files changed

+62
-8
lines changed

entrypoints/background.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import '@/utils/rpc'
44
import { browser } from 'wxt/browser'
55
import { defineBackground } from 'wxt/utils/define-background'
66

7+
import { INVALID_URLS } from '@/utils/constants'
78
import { CONTEXT_MENU } from '@/utils/context-menu'
89
import logger from '@/utils/logger'
910
import { bgBroadcastRpc } from '@/utils/rpc'
@@ -36,7 +37,7 @@ export default defineBackground(() => {
3637

3738
const setPopupStatusBasedOnUrl = async (tabId: number, url: string) => {
3839
const isValidUrl = /https?:\/\//.test(url ?? '')
39-
if (!isValidUrl || unAttachedTabs.has(tabId)) {
40+
if (!isValidUrl || unAttachedTabs.has(tabId) || INVALID_URLS.some((regex) => regex.test(url))) {
4041
await browser.action.setPopup({ popup: 'popup.html' })
4142
}
4243
else {

entrypoints/content/utils/chat/chat.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import EventEmitter from 'events'
22
import { type Ref, ref, watch } from 'vue'
33

4+
import { nonNullable } from '@/utils/array'
45
import { parseDocument } from '@/utils/document-parser'
56
import { AbortError, AppError } from '@/utils/error'
67
import logger from '@/utils/logger'
@@ -296,7 +297,7 @@ export class Chat {
296297
const relevantTabIds = this.contextTabs.value.map((tab) => tab.tabId)
297298
const pages = await getDocumentContentOfTabs(relevantTabIds)
298299
const abortController = this.createAbortController()
299-
const prompt = await nextStep(contextMsgs, pages)
300+
const prompt = await nextStep(contextMsgs, pages.filter(nonNullable))
300301
const next = await generateObjectInBackground({
301302
schema: 'nextStep',
302303
prompt: prompt.user,
@@ -404,7 +405,7 @@ export class Chat {
404405
const abortController = this.createAbortController()
405406
const relevantTabIds = this.contextTabs.value.map((tab) => tab.tabId)
406407
const pages = await getDocumentContentOfTabs(relevantTabIds)
407-
const prompt = await generateSearchKeywords(contextMsgs, pages)
408+
const prompt = await generateSearchKeywords(contextMsgs, pages.filter(nonNullable))
408409
const r = await generateObjectInBackground({
409410
schema: 'searchKeywords',
410411
system: prompt.system,
@@ -459,7 +460,7 @@ export class Chat {
459460
}
460461
const relevantTabIds = this.contextTabs.value.map((tab) => tab.tabId)
461462
const pages = await getDocumentContentOfTabs(relevantTabIds)
462-
const prompt = await chatWithPageContent(question, pages, onlineResults)
463+
const prompt = await chatWithPageContent(question, pages.filter(nonNullable), onlineResults)
463464
await this.sendMessage(prompt.user, prompt.system, { assistantMsg: loading })
464465
}
465466

entrypoints/content/utils/tabs.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1+
import { INVALID_URLS } from '@/utils/constants'
12
import { parseDocument } from '@/utils/document-parser'
3+
import logger from '@/utils/logger'
24
import { c2bRpc } from '@/utils/rpc'
35
import { getTabStore } from '@/utils/tab-store'
6+
import { timeout } from '@/utils/timeout'
7+
8+
const log = logger.child('tabs')
49

510
export type TabInfo = {
611
tabId: number
@@ -15,7 +20,10 @@ export type TabContent = TabInfo & {
1520

1621
export async function getValidTabs(): Promise<TabInfo[]> {
1722
const tabs = await c2bRpc.getAllTabs()
18-
return tabs.filter((tab) => tab.url?.startsWith('http') && tab.tabId) as TabInfo[]
23+
return tabs.filter((tab) => {
24+
const { url, tabId } = tab
25+
return url && url.startsWith('http') && tabId && !INVALID_URLS.some((regex) => regex.test(url))
26+
}) as TabInfo[]
1927
}
2028

2129
export async function getTabInfo(tabId: number) {
@@ -24,7 +32,10 @@ export async function getTabInfo(tabId: number) {
2432
}
2533

2634
export async function getDocumentContentOfTabs(tabIds: number[]) {
27-
const contents = await Promise.all(tabIds.map((tabId) => c2bRpc.getDocumentContentOfTab(tabId)))
35+
const contents = await Promise.all(tabIds.map((tabId) => timeout(c2bRpc.getDocumentContentOfTab(tabId), 5000).catch(() => {
36+
log.error(`Failed to get content for tab ${tabId}, it might not be a valid HTML page or the tab is closed.`)
37+
return undefined
38+
})))
2839
return contents
2940
}
3041

entrypoints/popup/App.vue

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22
import { ref } from 'vue'
33
import { browser } from 'wxt/browser'
44
5+
import { INVALID_URLS } from '@/utils/constants'
6+
57
const isValidUrl = ref(false)
68
browser.tabs.query({ active: true, currentWindow: true }).then(async (tabs) => {
79
if (tabs.length === 1) {
810
const tab = tabs[0]
9-
isValidUrl.value = /https?:\/\//.test(tab.url ?? '')
11+
const { url } = tab || {}
12+
isValidUrl.value = !!url && /https?:\/\//.test(url) && !INVALID_URLS.some((regexp) => regexp.test(url))
1013
}
1114
})
1215
</script>

utils/array.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,7 @@ export class ObservableArray<T> extends Array<T> {
8383
this.eventEmitter.emit('change', this, oldArray)
8484
}
8585
}
86+
87+
export function nonNullable<T>(value: T | null | undefined): value is T {
88+
return value !== null && value !== undefined
89+
}

utils/constants.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,8 @@ export const VERSION = version.split('-')[0]
55
export const OLLAMA_TUTORIAL_URL = 'https://nativemind.app/blog/tutorial/ollama-setup'
66
export const OLLAMA_DOWNLOAD_URL = 'https://ollama.com/download'
77
export const OLLAMA_HOMEPAGE_URL = 'https://ollama.com'
8+
9+
export const INVALID_URLS = [
10+
/^https:\/\/chromewebstore.google.com/,
11+
/^https:\/\/chrome.google.com\/webstore\//,
12+
]

utils/error.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export type ErrorCode = 'unknown' | 'requestError' | 'requestTimeout' | 'abortError'
1+
export type ErrorCode = 'unknown' | 'requestError' | 'requestTimeout' | 'abortError' | 'timeoutError'
22

33
export abstract class AppError<Code extends ErrorCode> extends Error {
44
private _appError = true
@@ -58,11 +58,23 @@ export class AbortError extends AppError<'abortError'> {
5858
}
5959
}
6060

61+
// common timeout error for various operations
62+
export class TimeoutError extends AppError<'timeoutError'> {
63+
constructor(message: string) {
64+
super('timeoutError', message)
65+
}
66+
67+
toLocaleMessage(): string {
68+
return 'Operation timed out: ' + this.message
69+
}
70+
}
71+
6172
const errors = {
6273
unknown: UnknownError,
6374
requestError: ModelRequestError,
6475
requestTimeout: ModelRequestTimeoutError,
6576
abortError: AbortError,
77+
timeoutError: TimeoutError,
6678
} satisfies Record<ErrorCode, typeof AppError<ErrorCode>>
6779

6880
export function fromError(error: unknown): AppError<ErrorCode> {

utils/timeout.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { TimeoutError } from './error'
2+
3+
export function timeout<R, E>(p: Promise<R>, ms: number, onTimeout?: () => E): Promise<R> {
4+
return new Promise((resolve, reject) => {
5+
const timer = setTimeout(() => {
6+
reject(onTimeout?.() || new TimeoutError(`Promise timed out after ${ms} ms`))
7+
}, ms)
8+
9+
p.then((result) => {
10+
clearTimeout(timer)
11+
resolve(result)
12+
}).catch((error) => {
13+
clearTimeout(timer)
14+
reject(error)
15+
})
16+
})
17+
}

0 commit comments

Comments
 (0)