fix(common): extension port to relay regression (#4806)

This commit is contained in:
Shreyas 2025-03-04 16:58:33 +05:30 committed by GitHub
parent baa1703309
commit 36d71fc127
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 130 additions and 8 deletions

View file

@ -39,14 +39,19 @@ import IconChrome from "~icons/brands/chrome"
import IconFirefox from "~icons/brands/firefox"
import IconCheckCircle from "~icons/lucide/check-circle"
import { useI18n } from "@composables/i18n"
import { ExtensionInterceptorService } from "~/platform/std/interceptors/extension"
import { computed } from "vue"
import { useService } from "dioc/vue"
import { ExtensionKernelInterceptorService } from "~/platform/std/kernel-interceptors/extension"
const t = useI18n()
const extensionService = useService(ExtensionInterceptorService)
const extensionService = useService(ExtensionKernelInterceptorService)
const extensionVersion = computed(() => {
const versionOption = extensionService.extensionVersion
return versionOption.value ? versionOption.value : null
})
const extensionVersion = extensionService.extensionVersion
const hasChromeExtInstalled = extensionService.chromeExtensionInstalled
const hasFirefoxExtInstalled = extensionService.firefoxExtensionInstalled
</script>

View file

@ -1,4 +1,4 @@
import { computed, markRaw } from "vue"
import { computed, markRaw, ref } from "vue"
import { Service } from "dioc"
import type { RelayRequest, RelayResponse } from "@hoppscotch/kernel"
import { body } from "@hoppscotch/kernel"
@ -14,6 +14,11 @@ import * as E from "fp-ts/Either"
import { getI18n } from "~/modules/i18n"
import { until } from "@vueuse/core"
import { preProcessRelayRequest } from "~/helpers/functional/preprocess"
import { browserIsChrome, browserIsFirefox } from "~/helpers/utils/userAgent"
export const cancelRunningExtensionRequest = async () => {
window.__POSTWOMAN_EXTENSION_HOOK__?.cancelRequest()
}
export class ExtensionKernelInterceptorService
extends Service
@ -23,8 +28,15 @@ export class ExtensionKernelInterceptorService
private readonly store = this.bind(KernelInterceptorExtensionStore)
public readonly id = "extension"
public readonly name = (t: ReturnType<typeof getI18n>) =>
t("interceptor.extension.name")
public readonly name = (t: ReturnType<typeof getI18n>) => {
const version = this.extensionVersion.value
if (this.extensionStatus.value === "available" && version) {
return `${t("settings.extensions")}: v${version.major}.${version.minor}`
}
return `${t("settings.extensions")}: ${t("settings.extension_ver_not_reported")}`
}
public readonly selectable = { type: "selectable" as const }
public readonly capabilities = {
method: new Set([
@ -60,19 +72,34 @@ export class ExtensionKernelInterceptorService
} as const
public readonly settingsEntry = markRaw({
title: (t: ReturnType<typeof getI18n>) =>
t("interceptor.extension.settings_title"),
title: (t: ReturnType<typeof getI18n>) => t("settings.extensions"),
component: SettingsExtension,
})
public readonly subtitle = markRaw(SettingsExtensionSubtitle)
public readonly extensionStatus = computed(
() => this.store.getSettings().status
)
public readonly extensionVersion = computed(
() => this.store.getSettings().extensionVersion
)
/**
* Whether the extension is installed in Chrome or not.
*/
public readonly chromeExtensionInstalled = computed(
() => this.extensionStatus.value === "available" && browserIsChrome()
)
/**
* Whether the extension is installed in Firefox or not.
*/
public readonly firefoxExtensionInstalled = computed(
() => this.extensionStatus.value === "available" && browserIsFirefox()
)
private async executeExtensionRequest(
request: RelayRequest
): Promise<E.Either<KernelInterceptorError, RelayResponse>> {
@ -111,6 +138,23 @@ export class ExtensionKernelInterceptorService
),
})
} catch (e) {
console.error(e)
if (e instanceof Error && "response" in e) {
const response = (e as any).response
if (response) {
return E.right({
status: response.status,
statusText: response.statusText,
headers: response.headers,
body: body.body(
response.data,
response.headers["content-type"]
),
})
}
}
return E.left({
humanMessage: {
heading: (t) => t("error.extension.heading"),

View file

@ -2,6 +2,7 @@ import { Service } from "dioc"
import { Store } from "~/kernel/store"
import * as E from "fp-ts/Either"
import * as O from "fp-ts/Option"
import { ref } from "vue"
const STORE_NAMESPACE = "interceptors.extension.v1"
const SETTINGS_KEY = "settings"
@ -13,6 +14,41 @@ export type ExtensionVersion = {
minor: number
}
export const defineSubscribableObject = <T extends object>(obj: T) => {
const proxyObject = {
...obj,
_subscribers: {} as {
// eslint-disable-next-line no-unused-vars
[key in keyof T]?: ((...args: any[]) => any)[]
},
subscribe(prop: keyof T, func: (...args: any[]) => any): void {
if (Array.isArray(this._subscribers[prop])) {
this._subscribers[prop]?.push(func)
} else {
this._subscribers[prop] = [func]
}
},
}
type SubscribableProxyObject = typeof proxyObject
return new Proxy(proxyObject, {
set(obj, prop, newVal) {
obj[prop as keyof SubscribableProxyObject] = newVal
const currentSubscribers = obj._subscribers[prop as keyof T]
if (Array.isArray(currentSubscribers)) {
for (const subscriber of currentSubscribers) {
subscriber(newVal)
}
}
return true
},
})
}
type ExtensionSettings = {
version: "v1"
status: ExtensionStatus
@ -34,6 +70,7 @@ export class KernelInterceptorExtensionStore extends Service {
public static readonly ID = "KERNEL_EXTENSION_INTERCEPTOR_STORE"
private settings: ExtensionSettings = { ...DEFAULT_SETTINGS }
private extensionPollIntervalId = ref<ReturnType<typeof setInterval>>()
async onServiceInit(): Promise<void> {
const initResult = await Store.init()
@ -76,6 +113,42 @@ export class KernelInterceptorExtensionStore extends Service {
this.updateSettings({ status })
}
)
} else {
const statusProxy = defineSubscribableObject({
status: "waiting" as ExtensionStatus,
})
window.__HOPP_EXTENSION_STATUS_PROXY__ = statusProxy
statusProxy.subscribe(
"status",
(status: ExtensionStatus) => this.updateSettings({ status })
)
/**
* Keeping identifying extension backward compatible
* We are assuming the default version is 0.24 or later. So if the extension exists, its identified immediately,
* then we use a poll to find the version, this will get the version for 0.24 and any other version
* of the extension, but will have a slight lag.
* 0.24 users will get the benefits of 0.24, while the extension won't break for the old users
*/
this.extensionPollIntervalId.value = setInterval(() => {
if (typeof window.__POSTWOMAN_EXTENSION_HOOK__ !== "undefined") {
if (this.extensionPollIntervalId.value)
clearInterval(this.extensionPollIntervalId.value)
const version = window.__POSTWOMAN_EXTENSION_HOOK__.getVersion()
this.updateSettings({ extensionVersion: version })
// When the version is not 0.24 or higher, the extension wont do this. so we have to do it manually
if (
version.major === 0 &&
version.minor <= 23 &&
window.__HOPP_EXTENSION_STATUS_PROXY__
) {
window.__HOPP_EXTENSION_STATUS_PROXY__.status = "available"
}
}
}, 2000)
}
}