Skip to content

Commit 60a288a

Browse files
committed
feat: add Firefox compatibility and fix LAN/WiFi IP connectivity
- Add webextension-polyfill for cross-browser API support - Replace chrome.* APIs with browser.* polyfill throughout codebase - Use <all_urls> host permission for Firefox LAN IP support - Add robust browser detection and Firefox-specific error handling - Fix connectivity to Ollama servers on local network IPs (192.168.x.x, etc.) Fixes Firefox connection issues with LAN/WiFi IPs by using proper host permissions and cross-browser API abstraction.
1 parent 7fe934c commit 60a288a

File tree

11 files changed

+104
-38
lines changed

11 files changed

+104
-38
lines changed

package.json

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "ollama-client",
33
"displayName": "Ollama Client - Chat with Local LLM Models",
4-
"version": "0.2.4",
4+
"version": "0.2.5",
55
"description": "Privacy-first Ollama Chrome extension to chat with local AI models like LLaMA, Mistral, Gemma — fully offline.",
66
"author": "Shishir Chaurasiya",
77
"keywords": [
@@ -107,6 +107,7 @@
107107
"tailwind-merge": "3.0.1",
108108
"tailwind-scrollbar": "^4.0.2",
109109
"tailwindcss-animate": "1.0.7",
110+
"webextension-polyfill": "^0.12.0",
110111
"zustand": "^5.0.8"
111112
},
112113
"devDependencies": {
@@ -135,10 +136,7 @@
135136
},
136137
"manifest": {
137138
"host_permissions": [
138-
"<all_urls>",
139-
"https://*/*",
140-
"http://localhost:11434/*",
141-
"http://localhost/*"
139+
"<all_urls>"
142140
],
143141
"permissions": [
144142
"storage",

pnpm-lock.yaml

Lines changed: 14 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/background/handlers/handle-update-base-url.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,22 @@
1+
import { isChromiumBased } from "@/lib/browser-api"
12
import type { SendResponseFunction } from "@/types"
23

34
export const handleUpdateBaseUrl = async (
45
payload: string,
56
sendResponse: SendResponseFunction
67
): Promise<void> => {
8+
if (!isChromiumBased()) {
9+
sendResponse({
10+
success: false,
11+
error: {
12+
status: 0,
13+
message:
14+
"Firefox requires manual OLLAMA_ORIGINS configuration. See settings for instructions."
15+
}
16+
})
17+
return
18+
}
19+
720
try {
821
const origin = new URL(payload).origin
922

src/background/index.ts

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import "webextension-polyfill"
2+
3+
import { browser, isChromiumBased } from "@/lib/browser-api"
14
import { MESSAGE_KEYS } from "@/lib/constants"
25
import { handleChatWithModel } from "@/background/handlers/handle-chat-with-model"
36
import { handleDeleteModel } from "@/background/handlers/handle-delete-model"
@@ -12,7 +15,6 @@ import { handleUnloadModel } from "@/background/handlers/handle-unload-model"
1215
import { handleUpdateBaseUrl } from "@/background/handlers/handle-update-base-url"
1316
import { abortAndClearController } from "@/background/lib/abort-controller-registry"
1417
import { updateDNRRules } from "@/background/lib/dnr"
15-
import { isChromiumBased } from "@/background/lib/utils"
1618
import type {
1719
ChatWithModelMessage,
1820
ChromeMessage,
@@ -24,34 +26,37 @@ import type {
2426
export {}
2527

2628
const openOllamaClient = () => {
27-
chrome.windows.create({
28-
url: chrome.runtime.getURL("sidepanel.html"),
29+
browser.windows.create({
30+
url: browser.runtime.getURL("sidepanel.html"),
2931
type: "popup",
3032
width: 420,
3133
height: 640
3234
})
3335
}
3436

35-
if (isChromiumBased() && "sidePanel" in chrome) {
36-
chrome.sidePanel
37+
if (isChromiumBased() && "sidePanel" in browser) {
38+
;(browser as any).sidePanel
3739
.setPanelBehavior({ openPanelOnActionClick: true })
38-
.catch((error) => console.error("SidePanel error:", error))
40+
.catch((error: Error) => console.error("SidePanel error:", error))
3941
} else {
40-
chrome.action.onClicked.addListener(() => {
41-
openOllamaClient()
42-
})
42+
const actionAPI = browser.action || (browser as any).browserAction
43+
if (actionAPI) {
44+
actionAPI.onClicked.addListener(() => {
45+
openOllamaClient()
46+
})
47+
}
4348
}
4449

4550
if (!isChromiumBased()) {
4651
console.warn("DNR not available: skipping CORS workaround (likely Firefox)")
4752
}
4853

4954
if (isChromiumBased()) {
50-
chrome.runtime.onInstalled.addListener(updateDNRRules)
51-
chrome.runtime.onStartup.addListener(updateDNRRules)
55+
browser.runtime.onInstalled.addListener(() => updateDNRRules())
56+
browser.runtime.onStartup.addListener(() => updateDNRRules())
5257
}
5358

54-
chrome.runtime.onConnect.addListener((port: ChromePort) => {
59+
browser.runtime.onConnect.addListener((port: ChromePort) => {
5560
let isPortClosed = false
5661

5762
const getPortStatus: PortStatusFunction = () => isPortClosed
@@ -84,7 +89,7 @@ chrome.runtime.onConnect.addListener((port: ChromePort) => {
8489
})
8590

8691
// Handle one-time message requests
87-
chrome.runtime.onMessage.addListener(
92+
browser.runtime.onMessage.addListener(
8893
(message: ChromeMessage, sender, sendResponse) => {
8994
switch (message.type) {
9095
case MESSAGE_KEYS.OLLAMA.GET_MODELS: {
@@ -100,7 +105,7 @@ chrome.runtime.onMessage.addListener(
100105
}
101106

102107
case MESSAGE_KEYS.BROWSER.OPEN_TAB: {
103-
chrome.tabs.query({}, (tabs) => {
108+
browser.tabs.query({}).then((tabs) => {
104109
console.log(tabs)
105110
sendResponse({ success: true, tabs })
106111
})

src/background/lib/dnr.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
1+
import { isChromiumBased } from "@/lib/browser-api"
12
import { STORAGE_KEYS } from "@/lib/constants"
23
import { plasmoGlobalStorage } from "@/lib/plasmo-global-storage"
34

45
export const updateDNRRules = async (): Promise<void> => {
6+
if (!isChromiumBased()) {
7+
console.warn(
8+
"DNR not available: Firefox requires OLLAMA_ORIGINS configuration"
9+
)
10+
return
11+
}
12+
513
try {
614
const baseUrl =
715
((await plasmoGlobalStorage.get(
@@ -36,6 +44,7 @@ export const updateDNRRules = async (): Promise<void> => {
3644
]
3745
})
3846
} catch (error) {
47+
// Don't throw - allow extension to continue without DNR
3948
console.error("Failed to update DNR rules:", error)
4049
}
4150
}

src/background/lib/utils.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,6 @@ import { STORAGE_KEYS } from "@/lib/constants"
22
import { plasmoGlobalStorage } from "@/lib/plasmo-global-storage"
33
import type { ChatStreamMessage, ChromePort, PullStreamMessage } from "@/types"
44

5-
export const isChromiumBased = () => {
6-
return (
7-
typeof chrome !== "undefined" &&
8-
typeof chrome.declarativeNetRequest !== "undefined"
9-
)
10-
}
11-
125
export const safePostMessage = (
136
port: ChromePort,
147
message: ChatStreamMessage | PullStreamMessage

src/components/settings-button.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
TooltipContent,
55
TooltipTrigger
66
} from "@/components/ui/tooltip"
7+
import browser from "@/lib/browser-api"
78
import { Settings } from "@/lib/lucide-icon"
89

910
export const SettingsButton = ({ showText = true }: { showText?: boolean }) => {
@@ -14,7 +15,7 @@ export const SettingsButton = ({ showText = true }: { showText?: boolean }) => {
1415
variant="link"
1516
size="sm"
1617
onClick={() => {
17-
chrome.runtime.openOptionsPage()
18+
browser.runtime.openOptionsPage()
1819
}}
1920
aria-label="Extension Settings">
2021
<Settings size="16" className="opacity-80" />

src/features/chat/hooks/use-ollama-stream.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useRef } from "react"
22

3+
import { browser } from "@/lib/browser-api"
34
import { ERROR_MESSAGES, MESSAGE_KEYS } from "@/lib/constants"
45
import type { ChatMessage } from "@/types"
56

@@ -19,11 +20,12 @@ export const useOllamaStream = ({
1920
setIsLoading,
2021
setIsStreaming
2122
}: UseOllamaStreamProps) => {
22-
const portRef = useRef<chrome.runtime.Port | null>(null)
23+
const portRef = useRef<browser.Runtime.Port | null>(null)
2324
const currentMessagesRef = useRef<ChatMessage[]>([])
2425

2526
const startStream = ({ model, messages }: StreamOptions) => {
26-
const port = chrome.runtime.connect({
27+
// Create port synchronously BEFORE any async operations
28+
const port = browser.runtime.connect({
2729
name: MESSAGE_KEYS.OLLAMA.STREAM_RESPONSE
2830
})
2931
portRef.current = port

src/features/model/components/ollama-base-url-settings.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
} from "@/components/ui/card"
1212
import { Input } from "@/components/ui/input"
1313
import { Label } from "@/components/ui/label"
14+
import { browser } from "@/lib/browser-api"
1415
import { MESSAGE_KEYS, STORAGE_KEYS } from "@/lib/constants"
1516
import { Check, ExternalLink, Loader2, Server } from "@/lib/lucide-icon"
1617
import { plasmoGlobalStorage } from "@/lib/plasmo-global-storage"
@@ -31,7 +32,7 @@ export const BaseUrlSettings = () => {
3132

3233
const handleSave = async () => {
3334
try {
34-
await chrome.runtime.sendMessage({
35+
await browser.runtime.sendMessage({
3536
type: MESSAGE_KEYS.OLLAMA.UPDATE_BASE_URL,
3637
payload: ollamaUrl
3738
})

src/features/model/hooks/use-ollama-pull.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
import { useEffect, useRef, useState } from "react"
22

3+
import { browser } from "@/lib/browser-api"
34
import { MESSAGE_KEYS } from "@/lib/constants"
45

56
export const useOllamaPull = () => {
67
const [progress, setProgress] = useState<string | null>(null)
78
const [pullingModel, setPullingModel] = useState<string | null>(null)
8-
const portRef = useRef<chrome.runtime.Port | null>(null)
9+
const portRef = useRef<browser.Runtime.Port | null>(null)
910

1011
const pullModel = (modelName: string) => {
1112
console.log("modelName: ", modelName)
1213
setPullingModel(modelName)
1314
setProgress("Starting...")
1415

15-
const port = chrome.runtime.connect({
16+
const port = browser.runtime.connect({
1617
name: MESSAGE_KEYS.OLLAMA.PULL_MODEL
1718
})
1819
portRef.current = port

0 commit comments

Comments
 (0)