feat: migrate ESLint to v9 across packages (#5773)
Co-authored-by: curiouscorrelation <curiouscorrelation@gmail.com>
This commit is contained in:
parent
992579e285
commit
27b817f627
88 changed files with 3223 additions and 3263 deletions
67
packages/hoppscotch-agent/eslint.config.mjs
Normal file
67
packages/hoppscotch-agent/eslint.config.mjs
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
import pluginVue from "eslint-plugin-vue"
|
||||||
|
import {
|
||||||
|
defineConfigWithVueTs,
|
||||||
|
vueTsConfigs,
|
||||||
|
} from "@vue/eslint-config-typescript"
|
||||||
|
import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended"
|
||||||
|
import globals from "globals"
|
||||||
|
|
||||||
|
export default defineConfigWithVueTs(
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
"**/*.d.ts",
|
||||||
|
"dist/**",
|
||||||
|
"node_modules/**",
|
||||||
|
"src-tauri/**",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
pluginVue.configs["flat/recommended"],
|
||||||
|
vueTsConfigs.recommended,
|
||||||
|
eslintPluginPrettierRecommended,
|
||||||
|
{
|
||||||
|
files: ["**/*.ts", "**/*.js", "**/*.vue"],
|
||||||
|
linterOptions: {
|
||||||
|
reportUnusedDisableDirectives: false,
|
||||||
|
},
|
||||||
|
languageOptions: {
|
||||||
|
sourceType: "module",
|
||||||
|
ecmaVersion: "latest",
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.node,
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
requireConfigFile: false,
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
semi: [2, "never"],
|
||||||
|
"no-console": "off",
|
||||||
|
"no-debugger": process.env.HOPP_LINT_FOR_PROD === "true" ? "error" : "warn",
|
||||||
|
"prettier/prettier":
|
||||||
|
process.env.HOPP_LINT_FOR_PROD === "true" ? "error" : "warn",
|
||||||
|
"vue/multi-word-component-names": "off",
|
||||||
|
"vue/no-side-effects-in-computed-properties": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": [
|
||||||
|
process.env.HOPP_LINT_FOR_PROD === "true" ? "error" : "warn",
|
||||||
|
{
|
||||||
|
args: "all",
|
||||||
|
argsIgnorePattern: "^_",
|
||||||
|
caughtErrors: "all",
|
||||||
|
caughtErrorsIgnorePattern: "^_",
|
||||||
|
destructuredArrayIgnorePattern: "^_",
|
||||||
|
varsIgnorePattern: "^_",
|
||||||
|
ignoreRestSiblings: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@typescript-eslint/no-unused-expressions": "off",
|
||||||
|
"@typescript-eslint/no-non-null-assertion": "off",
|
||||||
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-function-type": "off",
|
||||||
|
"no-undef": "off",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
@ -7,7 +7,14 @@
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vue-tsc --noEmit && vite build",
|
"build": "vue-tsc --noEmit && vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"tauri": "tauri"
|
"tauri": "tauri",
|
||||||
|
"lint": "eslint src",
|
||||||
|
"lint:ts": "vue-tsc --noEmit",
|
||||||
|
"lintfix": "eslint --fix src",
|
||||||
|
"prod-lint": "cross-env HOPP_LINT_FOR_PROD=true pnpm run lint",
|
||||||
|
"do-lint": "pnpm run prod-lint",
|
||||||
|
"do-typecheck": "pnpm run lint:ts",
|
||||||
|
"do-lintfix": "pnpm run lintfix"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hoppscotch/ui": "0.2.5",
|
"@hoppscotch/ui": "0.2.5",
|
||||||
|
|
@ -24,8 +31,16 @@
|
||||||
"@tauri-apps/cli": "2.9.3",
|
"@tauri-apps/cli": "2.9.3",
|
||||||
"@types/lodash-es": "4.17.12",
|
"@types/lodash-es": "4.17.12",
|
||||||
"@types/node": "24.10.1",
|
"@types/node": "24.10.1",
|
||||||
|
"@typescript-eslint/eslint-plugin": "8.50.0",
|
||||||
|
"@typescript-eslint/parser": "8.50.0",
|
||||||
"@vitejs/plugin-vue": "6.0.3",
|
"@vitejs/plugin-vue": "6.0.3",
|
||||||
|
"@vue/eslint-config-typescript": "14.6.0",
|
||||||
"autoprefixer": "10.4.23",
|
"autoprefixer": "10.4.23",
|
||||||
|
"cross-env": "10.1.0",
|
||||||
|
"eslint": "9.39.2",
|
||||||
|
"eslint-plugin-prettier": "5.5.4",
|
||||||
|
"eslint-plugin-vue": "10.6.2",
|
||||||
|
"globals": "16.5.0",
|
||||||
"postcss": "8.5.6",
|
"postcss": "8.5.6",
|
||||||
"tailwindcss": "3.4.16",
|
"tailwindcss": "3.4.16",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
|
|
|
||||||
|
|
@ -5,21 +5,33 @@
|
||||||
<template v-if="O.isSome(state().otp)">
|
<template v-if="O.isSome(state().otp)">
|
||||||
<div class="flex-grow">
|
<div class="flex-grow">
|
||||||
<p class="tracking-wide">
|
<p class="tracking-wide">
|
||||||
An app is trying to register against the Hoppscotch Agent. If this was intentional, copy the given code into
|
An app is trying to register against the Hoppscotch Agent. If this was
|
||||||
the app to complete the registration process. Please cancel the registration if you did not initiate this request.
|
intentional, copy the given code into the app to complete the
|
||||||
The window will hide automatically once registration succeeds. If you minimize this window during registration,
|
registration process. Please cancel the registration if you did not
|
||||||
you can access it again from the tray by selecting "Maximize Window".
|
initiate this request. The window will hide automatically once
|
||||||
|
registration succeeds. If you minimize this window during
|
||||||
|
registration, you can access it again from the tray by selecting
|
||||||
|
"Maximize Window".
|
||||||
</p>
|
</p>
|
||||||
<p
|
<p
|
||||||
class="font-bold text-5xl tracking-wider text-center pt-10 text-white"
|
class="font-bold text-5xl tracking-wider text-center pt-10 text-white"
|
||||||
>{{ pipe(state().otp, O.getOrElse(() => "")) }}</p>
|
>
|
||||||
|
{{
|
||||||
|
pipe(
|
||||||
|
state().otp,
|
||||||
|
O.getOrElse(() => "")
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="flex-grow overflow-auto">
|
<div class="flex-grow overflow-auto">
|
||||||
<HoppSmartTable :headings="tableHeadings" :list="state().registrations">
|
<HoppSmartTable :headings="tableHeadings" :list="state().registrations">
|
||||||
<template #registered_at="{ item }">{{ formatDate(String(item.registered_at)) }}</template>
|
<template #registered_at="{ item }">{{
|
||||||
|
formatDate(String(item.registered_at))
|
||||||
|
}}</template>
|
||||||
</HoppSmartTable>
|
</HoppSmartTable>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -47,12 +59,12 @@
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<HoppButtonPrimary
|
<HoppButtonPrimary
|
||||||
v-else
|
v-else
|
||||||
label="Minimize to Tray"
|
label="Minimize to Tray"
|
||||||
outline
|
outline
|
||||||
@click="hideWindow"
|
@click="hideWindow"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -65,9 +77,9 @@ import {
|
||||||
HoppButtonSecondary,
|
HoppButtonSecondary,
|
||||||
HoppSmartTable,
|
HoppSmartTable,
|
||||||
} from "@hoppscotch/ui"
|
} from "@hoppscotch/ui"
|
||||||
// @ts-ignore
|
// @ts-expect-error - Icon import has no types
|
||||||
import IconCopy from "~icons/lucide/copy"
|
import IconCopy from "~icons/lucide/copy"
|
||||||
// @ts-ignore
|
// @ts-expect-error - Icon import has no types
|
||||||
import IconCheck from "~icons/lucide/check"
|
import IconCheck from "~icons/lucide/check"
|
||||||
import { useClipboard, refAutoReset } from "@vueuse/core"
|
import { useClipboard, refAutoReset } from "@vueuse/core"
|
||||||
import { getCurrentWindow } from "@tauri-apps/api/window"
|
import { getCurrentWindow } from "@tauri-apps/api/window"
|
||||||
|
|
@ -183,7 +195,7 @@ onMounted(async () => {
|
||||||
if (otp) {
|
if (otp) {
|
||||||
appState.value = { ...state(), otp: O.some(otp) }
|
appState.value = { ...state(), otp: O.some(otp) }
|
||||||
} else {
|
} else {
|
||||||
updateRegistrations();
|
updateRegistrations()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)()
|
)()
|
||||||
|
|
@ -204,12 +216,12 @@ onMounted(async () => {
|
||||||
getOtp,
|
getOtp,
|
||||||
TE.map((otp: string) => {
|
TE.map((otp: string) => {
|
||||||
if (otp) {
|
if (otp) {
|
||||||
appState.value = { ...state(), otp: O.some(otp) };
|
appState.value = { ...state(), otp: O.some(otp) }
|
||||||
} else {
|
} else {
|
||||||
updateRegistrations();
|
updateRegistrations()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)();
|
)()
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { createApp } from 'vue'
|
import { createApp } from "vue"
|
||||||
import App from './App.vue'
|
import App from "./App.vue"
|
||||||
import './index.css'
|
import "./index.css"
|
||||||
|
|
||||||
import { plugin as HoppUI } from "@hoppscotch/ui"
|
import { plugin as HoppUI } from "@hoppscotch/ui"
|
||||||
|
|
||||||
|
|
@ -8,6 +8,4 @@ import "@hoppscotch/ui/themes.css"
|
||||||
|
|
||||||
import "@hoppscotch/ui/style.css"
|
import "@hoppscotch/ui/style.css"
|
||||||
|
|
||||||
createApp(App)
|
createApp(App).use(HoppUI).mount("#app")
|
||||||
.use(HoppUI)
|
|
||||||
.mount('#app')
|
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,21 @@
|
||||||
<h1 class="font-bold text-lg text-white">Agent Registration Request</h1>
|
<h1 class="font-bold text-lg text-white">Agent Registration Request</h1>
|
||||||
<div v-if="otpCode">
|
<div v-if="otpCode">
|
||||||
<p class="tracking-wide">
|
<p class="tracking-wide">
|
||||||
An app is trying to register against the Hoppscotch Agent. If this was intentional, copy the given code into
|
An app is trying to register against the Hoppscotch Agent. If this was
|
||||||
the app to complete the registration process. Please hide the window if you did not initiate this request.
|
intentional, copy the given code into the app to complete the
|
||||||
Do not hide this window until the verification code is entered. The window will hide automatically once done.
|
registration process. Please hide the window if you did not initiate
|
||||||
|
this request. Do not hide this window until the verification code is
|
||||||
|
entered. The window will hide automatically once done.
|
||||||
|
</p>
|
||||||
|
<p class="font-bold text-5xl tracking-wider text-center pt-10 text-white">
|
||||||
|
{{ otpCode }}
|
||||||
</p>
|
</p>
|
||||||
<p class="font-bold text-5xl tracking-wider text-center pt-10 text-white">{{ otpCode }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="text-center pt-10">
|
<div v-else class="text-center pt-10">
|
||||||
<p class="tracking-wide">Waiting for registration requests...</p>
|
<p class="tracking-wide">Waiting for registration requests...</p>
|
||||||
<p
|
<p class="text-sm text-gray-400 mt-2">
|
||||||
class="text-sm text-gray-400 mt-2"
|
You can hide this window and access it again from the tray icon.
|
||||||
>You can hide this window and access it again from the tray icon.</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="border-t border-divider p-5 flex justify-between">
|
<div class="border-t border-divider p-5 flex justify-between">
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
|
|
@ -35,10 +39,12 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { ref, markRaw, onMounted } from "vue"
|
import { ref, markRaw, onMounted } from "vue"
|
||||||
import { HoppButtonPrimary, HoppButtonSecondary } from "@hoppscotch/ui"
|
import { HoppButtonSecondary } from "@hoppscotch/ui"
|
||||||
|
// @ts-expect-error - Icon import has no types
|
||||||
import IconCopy from "~icons/lucide/copy"
|
import IconCopy from "~icons/lucide/copy"
|
||||||
|
// @ts-expect-error - Icon import has no types
|
||||||
import IconCheck from "~icons/lucide/check"
|
import IconCheck from "~icons/lucide/check"
|
||||||
import { useClipboard, refAutoReset } from "@vueuse/core"
|
import { useClipboard, refAutoReset } from "@vueuse/core"
|
||||||
import { getCurrentWindow } from "@tauri-apps/api/window"
|
import { getCurrentWindow } from "@tauri-apps/api/window"
|
||||||
|
|
@ -64,12 +70,12 @@ onMounted(async () => {
|
||||||
const currentWindow = getCurrentWindow()
|
const currentWindow = getCurrentWindow()
|
||||||
currentWindow.setAlwaysOnTop(true)
|
currentWindow.setAlwaysOnTop(true)
|
||||||
|
|
||||||
const initialOtp = await invoke("get_otp", {})
|
const initialOtp = await invoke<string>("get_otp", {})
|
||||||
if (initialOtp) {
|
if (initialOtp) {
|
||||||
otpCode.value = initialOtp
|
otpCode.value = initialOtp
|
||||||
}
|
}
|
||||||
|
|
||||||
await listen("registration-received", (event) => {
|
await listen<string>("registration-received", (event) => {
|
||||||
otpCode.value = event.payload
|
otpCode.value = event.payload
|
||||||
currentWindow.setFocus()
|
currentWindow.setFocus()
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,14 @@
|
||||||
<div class="overflow-auto">
|
<div class="overflow-auto">
|
||||||
<HoppSmartTable
|
<HoppSmartTable
|
||||||
:headings="[
|
:headings="[
|
||||||
{ key: 'auth_key_hash', label: 'ID' },
|
{ key: 'auth_key_hash', label: 'ID' },
|
||||||
{ key: 'registered_at', label: 'Registered At' },
|
{ key: 'registered_at', label: 'Registered At' },
|
||||||
]"
|
]"
|
||||||
:list="registrations"
|
:list="registrations"
|
||||||
>
|
>
|
||||||
<template #registered_at="{ item }">{{ formatDate(item.registered_at) }}</template>
|
<template #registered_at="{ item }">{{
|
||||||
|
formatDate(item.registered_at)
|
||||||
|
}}</template>
|
||||||
</HoppSmartTable>
|
</HoppSmartTable>
|
||||||
</div>
|
</div>
|
||||||
<div class="border-t border-divider p-5 flex justify-between">
|
<div class="border-t border-divider p-5 flex justify-between">
|
||||||
|
|
@ -18,17 +20,26 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { ref, markRaw, onMounted } from "vue"
|
import { ref, onMounted } from "vue"
|
||||||
import { HoppButtonPrimary, HoppSmartTable } from "@hoppscotch/ui"
|
import { HoppButtonPrimary, HoppSmartTable } from "@hoppscotch/ui"
|
||||||
import { getCurrentWindow } from "@tauri-apps/api/window"
|
import { getCurrentWindow } from "@tauri-apps/api/window"
|
||||||
import { invoke } from "@tauri-apps/api/core"
|
import { invoke } from "@tauri-apps/api/core"
|
||||||
import { listen } from "@tauri-apps/api/event"
|
import { listen } from "@tauri-apps/api/event"
|
||||||
import { orderBy } from "lodash-es"
|
import { orderBy } from "lodash-es"
|
||||||
|
|
||||||
const registrations = ref([])
|
interface Registration {
|
||||||
|
auth_key_hash: string
|
||||||
|
registered_at: string
|
||||||
|
}
|
||||||
|
|
||||||
function formatDate(date) {
|
interface ListRegistrationsResult {
|
||||||
|
registrations: Registration[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const registrations = ref<Registration[]>([])
|
||||||
|
|
||||||
|
function formatDate(date: string): string {
|
||||||
return new Date(date).toLocaleString()
|
return new Date(date).toLocaleString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,7 +49,7 @@ function hideWindow() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadRegistrations() {
|
async function loadRegistrations() {
|
||||||
const result = await invoke("list_registrations", {})
|
const result = await invoke<ListRegistrationsResult>("list_registrations", {})
|
||||||
registrations.value = orderBy(result.registrations, "registered_at", "desc")
|
registrations.value = orderBy(result.registrations, "registered_at", "desc")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
/* eslint-env node */
|
|
||||||
require("@rushstack/eslint-patch/modern-module-resolution")
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
env: {
|
|
||||||
browser: true,
|
|
||||||
node: true,
|
|
||||||
},
|
|
||||||
parserOptions: {
|
|
||||||
sourceType: "module",
|
|
||||||
requireConfigFile: false,
|
|
||||||
ecmaFeatures: {
|
|
||||||
jsx: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
extends: [
|
|
||||||
"@vue/typescript/recommended",
|
|
||||||
"plugin:vue/recommended",
|
|
||||||
"plugin:prettier/recommended",
|
|
||||||
],
|
|
||||||
ignorePatterns: [
|
|
||||||
"static/**/*",
|
|
||||||
"./helpers/backend/graphql.ts",
|
|
||||||
"**/*.d.ts",
|
|
||||||
"types/**/*",
|
|
||||||
],
|
|
||||||
plugins: ["vue", "prettier"],
|
|
||||||
// add your custom rules here
|
|
||||||
rules: {
|
|
||||||
semi: [2, "never"],
|
|
||||||
"import/named": "off", // because, named import issue with typescript see: https://github.com/typescript-eslint/typescript-eslint/issues/154
|
|
||||||
"no-console": "off",
|
|
||||||
"no-debugger": process.env.HOPP_LINT_FOR_PROD === "true" ? "error" : "warn",
|
|
||||||
"prettier/prettier": [
|
|
||||||
process.env.HOPP_LINT_FOR_PROD === "true" ? "error" : "warn",
|
|
||||||
{
|
|
||||||
semi: false,
|
|
||||||
trailingComma: "es5",
|
|
||||||
singleQuote: false,
|
|
||||||
printWidth: 80,
|
|
||||||
useTabs: false,
|
|
||||||
tabWidth: 2,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"vue/multi-word-component-names": "off",
|
|
||||||
"vue/no-side-effects-in-computed-properties": "off",
|
|
||||||
"import/no-named-as-default": "off",
|
|
||||||
"import/no-named-as-default-member": "off",
|
|
||||||
"@typescript-eslint/no-unused-vars": [
|
|
||||||
process.env.HOPP_LINT_FOR_PROD === "true" ? "error" : "warn",
|
|
||||||
{
|
|
||||||
argsIgnorePattern: "^_",
|
|
||||||
varsIgnorePattern: "^_",
|
|
||||||
caughtErrorsIgnorePattern: "^_",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"@typescript-eslint/no-non-null-assertion": "off",
|
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
|
||||||
"import/default": "off",
|
|
||||||
"no-undef": "off",
|
|
||||||
// localStorage block
|
|
||||||
"no-restricted-globals": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
name: "localStorage",
|
|
||||||
message:
|
|
||||||
"Do not use 'localStorage' directly. Please use the PersistenceService",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
// window.localStorage block
|
|
||||||
"no-restricted-syntax": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
selector: "CallExpression[callee.object.property.name='localStorage']",
|
|
||||||
message:
|
|
||||||
"Do not use 'localStorage' directly. Please use the PersistenceService",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
eqeqeq: 1,
|
|
||||||
"no-else-return": 1,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
96
packages/hoppscotch-common/eslint.config.mjs
Normal file
96
packages/hoppscotch-common/eslint.config.mjs
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
import pluginVue from "eslint-plugin-vue"
|
||||||
|
import {
|
||||||
|
defineConfigWithVueTs,
|
||||||
|
vueTsConfigs,
|
||||||
|
} from "@vue/eslint-config-typescript"
|
||||||
|
import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended"
|
||||||
|
import globals from "globals"
|
||||||
|
|
||||||
|
export default defineConfigWithVueTs(
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
"static/**",
|
||||||
|
"src/helpers/backend/graphql.ts",
|
||||||
|
"**/*.d.ts",
|
||||||
|
"types/**",
|
||||||
|
"dist/**",
|
||||||
|
"node_modules/**",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
pluginVue.configs["flat/recommended"],
|
||||||
|
vueTsConfigs.recommended,
|
||||||
|
eslintPluginPrettierRecommended,
|
||||||
|
{
|
||||||
|
files: ["**/*.ts", "**/*.js", "**/*.vue"],
|
||||||
|
linterOptions: {
|
||||||
|
reportUnusedDisableDirectives: false,
|
||||||
|
},
|
||||||
|
languageOptions: {
|
||||||
|
sourceType: "module",
|
||||||
|
ecmaVersion: "latest",
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.node,
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
requireConfigFile: false,
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
semi: [2, "never"],
|
||||||
|
"import/named": "off",
|
||||||
|
"no-console": "off",
|
||||||
|
"no-debugger": process.env.HOPP_LINT_FOR_PROD === "true" ? "error" : "warn",
|
||||||
|
"prettier/prettier": [
|
||||||
|
process.env.HOPP_LINT_FOR_PROD === "true" ? "error" : "warn",
|
||||||
|
{
|
||||||
|
semi: false,
|
||||||
|
trailingComma: "es5",
|
||||||
|
singleQuote: false,
|
||||||
|
printWidth: 80,
|
||||||
|
useTabs: false,
|
||||||
|
tabWidth: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"vue/multi-word-component-names": "off",
|
||||||
|
"vue/no-side-effects-in-computed-properties": "off",
|
||||||
|
"import/no-named-as-default": "off",
|
||||||
|
"import/no-named-as-default-member": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": [
|
||||||
|
process.env.HOPP_LINT_FOR_PROD === "true" ? "error" : "warn",
|
||||||
|
{
|
||||||
|
argsIgnorePattern: "^_",
|
||||||
|
varsIgnorePattern: "^_",
|
||||||
|
caughtErrorsIgnorePattern: "^_",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@typescript-eslint/no-unused-expressions": "off",
|
||||||
|
"@typescript-eslint/no-non-null-assertion": "off",
|
||||||
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-function-type": "off",
|
||||||
|
"import/default": "off",
|
||||||
|
"no-undef": "off",
|
||||||
|
"no-restricted-globals": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
name: "localStorage",
|
||||||
|
message:
|
||||||
|
"Do not use 'localStorage' directly. Please use the PersistenceService",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"no-restricted-syntax": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
selector: "CallExpression[callee.object.property.name='localStorage']",
|
||||||
|
message:
|
||||||
|
"Do not use 'localStorage' directly. Please use the PersistenceService",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
eqeqeq: 1,
|
||||||
|
"no-else-return": 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
@ -8,10 +8,10 @@
|
||||||
"test:watch": "vitest",
|
"test:watch": "vitest",
|
||||||
"dev:vite": "vite",
|
"dev:vite": "vite",
|
||||||
"dev:gql-codegen": "graphql-codegen --require dotenv/config --config gql-codegen.yml --watch dotenv_config_path=\"../../.env\"",
|
"dev:gql-codegen": "graphql-codegen --require dotenv/config --config gql-codegen.yml --watch dotenv_config_path=\"../../.env\"",
|
||||||
"lint": "eslint src --ext .ts,.js,.vue --ignore-path .gitignore .",
|
"lint": "eslint src",
|
||||||
"lint:ts": "vue-tsc --noEmit",
|
"lint:ts": "vue-tsc --noEmit",
|
||||||
"prod-lint": "cross-env HOPP_LINT_FOR_PROD=true pnpm run lint",
|
"prod-lint": "cross-env HOPP_LINT_FOR_PROD=true pnpm run lint",
|
||||||
"lintfix": "eslint --fix src --ext .ts,.js,.vue --ignore-path .gitignore .",
|
"lintfix": "eslint --fix src",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"gql-codegen": "graphql-codegen --require dotenv/config --config gql-codegen.yml dotenv_config_path=\"../../.env\"",
|
"gql-codegen": "graphql-codegen --require dotenv/config --config gql-codegen.yml dotenv_config_path=\"../../.env\"",
|
||||||
"postinstall": "pnpm run gql-codegen",
|
"postinstall": "pnpm run gql-codegen",
|
||||||
|
|
@ -127,6 +127,8 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@esbuild-plugins/node-globals-polyfill": "0.2.3",
|
"@esbuild-plugins/node-globals-polyfill": "0.2.3",
|
||||||
"@esbuild-plugins/node-modules-polyfill": "0.2.2",
|
"@esbuild-plugins/node-modules-polyfill": "0.2.2",
|
||||||
|
"@eslint/eslintrc": "3.3.3",
|
||||||
|
"@eslint/js": "9.39.2",
|
||||||
"@graphql-codegen/add": "6.0.0",
|
"@graphql-codegen/add": "6.0.0",
|
||||||
"@graphql-codegen/cli": "6.1.0",
|
"@graphql-codegen/cli": "6.1.0",
|
||||||
"@graphql-codegen/typed-document-node": "6.1.5",
|
"@graphql-codegen/typed-document-node": "6.1.5",
|
||||||
|
|
@ -153,15 +155,16 @@
|
||||||
"@typescript-eslint/parser": "8.50.0",
|
"@typescript-eslint/parser": "8.50.0",
|
||||||
"@vitejs/plugin-vue": "6.0.3",
|
"@vitejs/plugin-vue": "6.0.3",
|
||||||
"@vue/compiler-sfc": "3.5.26",
|
"@vue/compiler-sfc": "3.5.26",
|
||||||
"@vue/eslint-config-typescript": "13.0.0",
|
"@vue/eslint-config-typescript": "14.6.0",
|
||||||
"@vue/runtime-core": "3.5.26",
|
"@vue/runtime-core": "3.5.26",
|
||||||
"autoprefixer": "10.4.23",
|
"autoprefixer": "10.4.23",
|
||||||
"cross-env": "10.1.0",
|
"cross-env": "10.1.0",
|
||||||
"dotenv": "17.2.3",
|
"dotenv": "17.2.3",
|
||||||
"eslint": "8.57.0",
|
"eslint": "9.39.2",
|
||||||
"eslint-plugin-prettier": "5.5.4",
|
"eslint-plugin-prettier": "5.5.4",
|
||||||
"eslint-plugin-vue": "10.6.2",
|
"eslint-plugin-vue": "10.6.2",
|
||||||
"glob": "13.0.0",
|
"glob": "13.0.0",
|
||||||
|
"globals": "16.5.0",
|
||||||
"jsdom": "27.3.0",
|
"jsdom": "27.3.0",
|
||||||
"npm-run-all": "4.1.5",
|
"npm-run-all": "4.1.5",
|
||||||
"openapi-types": "12.1.3",
|
"openapi-types": "12.1.3",
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ const fetchAccessTokens = async () => {
|
||||||
if (tokensListFetchErrored.value) {
|
if (tokensListFetchErrored.value) {
|
||||||
tokensListFetchErrored.value = false
|
tokensListFetchErrored.value = false
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (_err) {
|
||||||
toast.error(t("error.fetching_access_tokens_list"))
|
toast.error(t("error.fetching_access_tokens_list"))
|
||||||
tokensListFetchErrored.value = true
|
tokensListFetchErrored.value = true
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -136,7 +136,7 @@ const generateAccessToken = async ({
|
||||||
if (tokensListFetchErrored.value) {
|
if (tokensListFetchErrored.value) {
|
||||||
tokensListFetchErrored.value = false
|
tokensListFetchErrored.value = false
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (_err) {
|
||||||
toast.error(t("error.generate_access_token"))
|
toast.error(t("error.generate_access_token"))
|
||||||
showAccessTokensGenerateModal.value = false
|
showAccessTokensGenerateModal.value = false
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -175,7 +175,7 @@ const deleteAccessToken = async () => {
|
||||||
if (tokensListFetchErrored.value) {
|
if (tokensListFetchErrored.value) {
|
||||||
tokensListFetchErrored.value = false
|
tokensListFetchErrored.value = false
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (_err) {
|
||||||
toast.error(t("error.delete_access_token"))
|
toast.error(t("error.delete_access_token"))
|
||||||
} finally {
|
} finally {
|
||||||
tokenDeleteActionLoading.value = false
|
tokenDeleteActionLoading.value = false
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,6 @@ watch(
|
||||||
doc: props.contentRight.content,
|
doc: props.contentRight.content,
|
||||||
extensions: [jsonLanguage, baseTheme, basicSetup],
|
extensions: [jsonLanguage, baseTheme, basicSetup],
|
||||||
},
|
},
|
||||||
// @ts-expect-error attribute mismatch
|
|
||||||
parent: diffEditor.value,
|
parent: diffEditor.value,
|
||||||
highlightChanges: false,
|
highlightChanges: false,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ const request = computed(() => {
|
||||||
return pathFolders.value[pathFolders.value.length - 1].requests[
|
return pathFolders.value[pathFolders.value.length - 1].requests[
|
||||||
requestIndex
|
requestIndex
|
||||||
]
|
]
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ const request = computed(() => {
|
||||||
return pathFolders.value[pathFolders.value.length - 1].requests[
|
return pathFolders.value[pathFolders.value.length - 1].requests[
|
||||||
requestIndex
|
requestIndex
|
||||||
]
|
]
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -253,7 +253,7 @@ const countPostmanScripts = (
|
||||||
if (collection?.item && Array.isArray(collection.item)) {
|
if (collection?.item && Array.isArray(collection.item)) {
|
||||||
collection.item.forEach(countInItem)
|
collection.item.forEach(countInItem)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
// Invalid JSON, skip
|
// Invalid JSON, skip
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -337,7 +337,7 @@ const HoppAllCollectionImporter: ImporterOrExporter = {
|
||||||
exporter: "import_to_teams",
|
exporter: "import_to_teams",
|
||||||
platform: "rest",
|
platform: "rest",
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
showImportFailedError()
|
showImportFailedError()
|
||||||
unsetCurrentImportSummary()
|
unsetCurrentImportSummary()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -342,7 +342,7 @@ const fetchTeamCollection = async () => {
|
||||||
props.collection as TeamCollection
|
props.collection as TeamCollection
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
fullCollectionData.value = teamCollToHoppRESTColl(
|
fullCollectionData.value = teamCollToHoppRESTColl(
|
||||||
props.collection as TeamCollection
|
props.collection as TeamCollection
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ function formatJSON(jsonString: string): string {
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(jsonString || "{}")
|
const parsed = JSON.parse(jsonString || "{}")
|
||||||
return JSON.stringify(parsed, null, 2)
|
return JSON.stringify(parsed, null, 2)
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
return jsonString || ""
|
return jsonString || ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -94,7 +94,7 @@ function formatJSON(jsonString: string): string {
|
||||||
function parseFormData(formData: string): { key: string; value: string }[] {
|
function parseFormData(formData: string): { key: string; value: string }[] {
|
||||||
try {
|
try {
|
||||||
return typeof formData === "string" ? parseRawKeyValueEntries(formData) : []
|
return typeof formData === "string" ? parseRawKeyValueEntries(formData) : []
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -212,7 +212,7 @@ function isJsonResponse(example: ResponseExample): boolean {
|
||||||
try {
|
try {
|
||||||
JSON.parse(example.body || "")
|
JSON.parse(example.body || "")
|
||||||
return true
|
return true
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -226,7 +226,7 @@ function formatJSON(jsonString: string): string {
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(jsonString || "{}")
|
const parsed = JSON.parse(jsonString || "{}")
|
||||||
return JSON.stringify(parsed, null, 2)
|
return JSON.stringify(parsed, null, 2)
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
return jsonString || ""
|
return jsonString || ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -174,7 +174,7 @@ const debouncedOnUpdateQueryState = debounce((update: ViewUpdate) => {
|
||||||
const { start, end } = def.loc!
|
const { start, end } = def.loc!
|
||||||
return selectedPos >= start && selectedPos <= end
|
return selectedPos >= start && selectedPos <= end
|
||||||
}) as OperationDefinitionNode) ?? null
|
}) as OperationDefinitionNode) ?? null
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
if (queryString.trim() === "") {
|
if (queryString.trim() === "") {
|
||||||
operationDefinitions.value = []
|
operationDefinitions.value = []
|
||||||
}
|
}
|
||||||
|
|
@ -193,7 +193,7 @@ onMounted(() => {
|
||||||
selectedOperation.value = ast.definitions[0] as OperationDefinitionNode
|
selectedOperation.value = ast.definitions[0] as OperationDefinitionNode
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} catch (error) {}
|
} catch (_error) {}
|
||||||
})
|
})
|
||||||
|
|
||||||
const cmQueryEditor = useCodemirror(
|
const cmQueryEditor = useCodemirror(
|
||||||
|
|
@ -238,7 +238,7 @@ const prettifyQuery = () => {
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
prettifyQueryIcon.value = IconCheck
|
prettifyQueryIcon.value = IconCheck
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
toast.error(`${t("error.gql_prettify_invalid_query")}`)
|
toast.error(`${t("error.gql_prettify_invalid_query")}`)
|
||||||
prettifyQueryIcon.value = IconInfo
|
prettifyQueryIcon.value = IconInfo
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,14 +77,14 @@ import { platform } from "~/platform"
|
||||||
import { KernelInterceptorService } from "~/services/kernel-interceptor.service"
|
import { KernelInterceptorService } from "~/services/kernel-interceptor.service"
|
||||||
import { GQLTabService } from "~/services/tab/graphql"
|
import { GQLTabService } from "~/services/tab/graphql"
|
||||||
|
|
||||||
const VALID_GQL_OPERATIONS = [
|
const _VALID_GQL_OPERATIONS = [
|
||||||
"query",
|
"query",
|
||||||
"headers",
|
"headers",
|
||||||
"variables",
|
"variables",
|
||||||
"authorization",
|
"authorization",
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
export type GQLOptionTabs = (typeof VALID_GQL_OPERATIONS)[number]
|
export type GQLOptionTabs = (typeof _VALID_GQL_OPERATIONS)[number]
|
||||||
|
|
||||||
const interceptorService = useService(KernelInterceptorService)
|
const interceptorService = useService(KernelInterceptorService)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -600,7 +600,7 @@ const saveRequest = async () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
toast.success(`${t("request.saved")}`)
|
toast.success(`${t("request.saved")}`)
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
tab.value.document.saveContext = undefined
|
tab.value.document.saveContext = undefined
|
||||||
saveRequest()
|
saveRequest()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,7 @@ import { defineActionHandler } from "~/helpers/actions"
|
||||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||||
import { AggregateEnvironment } from "~/newstore/environments"
|
import { AggregateEnvironment } from "~/newstore/environments"
|
||||||
|
|
||||||
const VALID_OPTION_TABS = [
|
const _VALID_OPTION_TABS = [
|
||||||
"params",
|
"params",
|
||||||
"bodyParams",
|
"bodyParams",
|
||||||
"headers",
|
"headers",
|
||||||
|
|
@ -120,7 +120,7 @@ const VALID_OPTION_TABS = [
|
||||||
"requestVariables",
|
"requestVariables",
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
export type RESTOptionTabs = (typeof VALID_OPTION_TABS)[number]
|
export type RESTOptionTabs = (typeof _VALID_OPTION_TABS)[number]
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -172,7 +172,7 @@ function getCurrentPageCategory(): "graphql" | "rest" | "other" {
|
||||||
return "rest"
|
return "rest"
|
||||||
}
|
}
|
||||||
return "other"
|
return "other"
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
return "other"
|
return "other"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -168,7 +168,7 @@ const retryWithProxy = async () => {
|
||||||
} else {
|
} else {
|
||||||
toast.error(t("import.failed"))
|
toast.error(t("import.failed"))
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
toast.error(t("import.failed"))
|
toast.error(t("import.failed"))
|
||||||
} finally {
|
} finally {
|
||||||
isFetchingUrl.value = false
|
isFetchingUrl.value = false
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ async function register() {
|
||||||
try {
|
try {
|
||||||
await agentService.initiateRegistration()
|
await agentService.initiateRegistration()
|
||||||
registrationStatus.value = "otp_required"
|
registrationStatus.value = "otp_required"
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
toast.error("Failed to initiate registration. Please try again.")
|
toast.error("Failed to initiate registration. Please try again.")
|
||||||
registrationStatus.value = "initial"
|
registrationStatus.value = "initial"
|
||||||
}
|
}
|
||||||
|
|
@ -85,7 +85,7 @@ async function verifyOTP(otp: string) {
|
||||||
await agentService.verifyRegistration(otp)
|
await agentService.verifyRegistration(otp)
|
||||||
toast.success("Registration successful!")
|
toast.success("Registration successful!")
|
||||||
hideModal()
|
hideModal()
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
toast.error("Failed to verify OTP. Please try again.")
|
toast.error("Failed to verify OTP. Please try again.")
|
||||||
registrationStatus.value = "otp_required"
|
registrationStatus.value = "otp_required"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -294,7 +294,7 @@ const copyToClipboardHandler = async (text: string) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
copyIcon.value = IconCopy
|
copyIcon.value = IconCopy
|
||||||
}, 1000)
|
}, 1000)
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
toast.error(t("error.copy_failed"))
|
toast.error(t("error.copy_failed"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -354,7 +354,7 @@ const copyToClipboardHandler = async (text: string) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
copyIcon.value = IconCopy
|
copyIcon.value = IconCopy
|
||||||
}, 1000)
|
}, 1000)
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
toast.error(t("error.copy_failed"))
|
toast.error(t("error.copy_failed"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -668,7 +668,7 @@ async function updateMaskedAuthKey() {
|
||||||
try {
|
try {
|
||||||
const registration = await store.fetchRegistrationInfo()
|
const registration = await store.fetchRegistrationInfo()
|
||||||
maskedAuthKey.value = registration.auth_key_hash
|
maskedAuthKey.value = registration.auth_key_hash
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
toast.error(t("settings.agent_registration_fetch_failed"))
|
toast.error(t("settings.agent_registration_fetch_failed"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,7 @@ const register = async () => {
|
||||||
await updateMaskedAuthKey()
|
await updateMaskedAuthKey()
|
||||||
toast.success(t("settings.agent_registration_successful"))
|
toast.success(t("settings.agent_registration_successful"))
|
||||||
store.registrationOTP.value = ""
|
store.registrationOTP.value = ""
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
} finally {
|
} finally {
|
||||||
store.isRegistering.value = false
|
store.isRegistering.value = false
|
||||||
}
|
}
|
||||||
|
|
@ -146,7 +146,7 @@ const updateMaskedAuthKey = async () => {
|
||||||
try {
|
try {
|
||||||
const registration = await store.fetchRegistrationInfo()
|
const registration = await store.fetchRegistrationInfo()
|
||||||
store.maskedAuthKey.value = registration.auth_key_hash
|
store.maskedAuthKey.value = registration.auth_key_hash
|
||||||
} catch (e) {}
|
} catch (_e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ export function initializeApp() {
|
||||||
platform.analytics?.initAnalytics()
|
platform.analytics?.initAnalytics()
|
||||||
|
|
||||||
initialized = true
|
initialized = true
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
// initializeApp throws exception if we reinitialize
|
// initializeApp throws exception if we reinitialize
|
||||||
initialized = true
|
initialized = true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ type TypeFromPrimitiveArray<P extends JSPrimitive | undefined> =
|
||||||
: P extends "symbol"
|
: P extends "symbol"
|
||||||
? symbol[]
|
? symbol[]
|
||||||
: P extends "function"
|
: P extends "function"
|
||||||
? Function[] // eslint-disable-line @typescript-eslint/ban-types
|
? Function[]
|
||||||
: unknown[]
|
: unknown[]
|
||||||
|
|
||||||
// The ban-types silence is because in this case,
|
// The ban-types silence is because in this case,
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ export type TypeFromPrimitive<P extends JSPrimitive | undefined> =
|
||||||
: P extends "symbol"
|
: P extends "symbol"
|
||||||
? symbol
|
? symbol
|
||||||
: P extends "function"
|
: P extends "function"
|
||||||
? Function // eslint-disable-line @typescript-eslint/ban-types
|
? Function
|
||||||
: unknown
|
: unknown
|
||||||
|
|
||||||
// The ban-types silence is because in this case,
|
// The ban-types silence is because in this case,
|
||||||
|
|
|
||||||
|
|
@ -206,7 +206,7 @@ const parseOpenAPIV3Responses = (
|
||||||
try {
|
try {
|
||||||
stringifiedBody = JSON.stringify(body ?? "")
|
stringifiedBody = JSON.stringify(body ?? "")
|
||||||
// the parsing will fail for a circular response schema
|
// the parsing will fail for a circular response schema
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
// eat five star, do nothing
|
// eat five star, do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -435,7 +435,7 @@ const parseOpenAPIV3Body = (
|
||||||
? sampleBody
|
? sampleBody
|
||||||
: JSON.stringify(sampleBody, null, 2),
|
: JSON.stringify(sampleBody, null, 2),
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
// If we can't generate a sample, check for examples
|
// If we can't generate a sample, check for examples
|
||||||
if (media.example !== undefined) {
|
if (media.example !== undefined) {
|
||||||
return {
|
return {
|
||||||
|
|
@ -1152,7 +1152,7 @@ export const hoppOpenAPIImporter = (fileContents: string[]) =>
|
||||||
try {
|
try {
|
||||||
const validatedDoc = await dereferenceDocs(docObj)
|
const validatedDoc = await dereferenceDocs(docObj)
|
||||||
resultDoc.push(validatedDoc)
|
resultDoc.push(validatedDoc)
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
// Check if the document has unresolved references
|
// Check if the document has unresolved references
|
||||||
if (hasUnresolvedRefs(docObj)) {
|
if (hasUnresolvedRefs(docObj)) {
|
||||||
console.warn(
|
console.warn(
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,11 @@ import * as E from "fp-ts/Either"
|
||||||
const validateDocs = async (docs: any) => {
|
const validateDocs = async (docs: any) => {
|
||||||
try {
|
try {
|
||||||
const res = await SwaggerParser.validate(docs, {
|
const res = await SwaggerParser.validate(docs, {
|
||||||
// @ts-expect-error - this is a valid option, but seems like the types are not updated
|
|
||||||
continueOnError: true,
|
continueOnError: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
return E.right(res)
|
return E.right(res)
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
return E.left("COULD_NOT_VALIDATE" as const)
|
return E.left("COULD_NOT_VALIDATE" as const)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -27,7 +26,7 @@ const dereferenceDocs = async (docs: any) => {
|
||||||
const res = await SwaggerParser.dereference(docs)
|
const res = await SwaggerParser.dereference(docs)
|
||||||
|
|
||||||
return E.right(res)
|
return E.right(res)
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
return E.left("COULD_NOT_DEREFERENCE" as const)
|
return E.left("COULD_NOT_DEREFERENCE" as const)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ export const isValidUser = async (): Promise<ValidUserResponse> => {
|
||||||
|
|
||||||
// For platforms without token verification capability
|
// For platforms without token verification capability
|
||||||
return { valid: true, error: "" }
|
return { valid: true, error: "" }
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
// Handle errors from token verification
|
// Handle errors from token verification
|
||||||
return attemptTokenRefresh()
|
return attemptTokenRefresh()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ export default function jsonParse(
|
||||||
const ast = parseObj()
|
const ast = parseObj()
|
||||||
expect("EOF")
|
expect("EOF")
|
||||||
return ast
|
return ast
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
// Try parsing expecting a root array
|
// Try parsing expecting a root array
|
||||||
const ast = parseArr()
|
const ast = parseArr()
|
||||||
expect("EOF")
|
expect("EOF")
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,7 @@ export default function jsonParse(
|
||||||
const ast = parseObj()
|
const ast = parseObj()
|
||||||
expect("EOF")
|
expect("EOF")
|
||||||
return ast
|
return ast
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
pendingComments = [] // Reset pending comments
|
pendingComments = [] // Reset pending comments
|
||||||
const ast = parseArr()
|
const ast = parseArr()
|
||||||
expect("EOF")
|
expect("EOF")
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ export function isValidJSONResponse(contents: string | ArrayBuffer): boolean {
|
||||||
try {
|
try {
|
||||||
JSON.parse(resolvedStr)
|
JSON.parse(resolvedStr)
|
||||||
return true
|
return true
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ export function createRESTNetworkRequestStream(
|
||||||
try {
|
try {
|
||||||
const result = await execResult
|
const result = await execResult
|
||||||
if (result) await result.cancel()
|
if (result) await result.cancel()
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
// Ignore cancel errors - request may have already completed
|
// Ignore cancel errors - request may have already completed
|
||||||
// This is expected behavior and not an actual error
|
// This is expected behavior and not an actual error
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ export function getJSONOutlineAtPos(
|
||||||
}
|
}
|
||||||
|
|
||||||
return path
|
return path
|
||||||
} catch (e: any) {
|
} catch (_e: any) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -195,7 +195,7 @@ export class MQTTConnection {
|
||||||
message,
|
message,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
this.addEvent({
|
this.addEvent({
|
||||||
time: Date.now(),
|
time: Date.now(),
|
||||||
type: "ERROR",
|
type: "ERROR",
|
||||||
|
|
@ -216,7 +216,7 @@ export class MQTTConnection {
|
||||||
onFailure: this.usubFailure.bind(this, topic.name),
|
onFailure: this.usubFailure.bind(this, topic.name),
|
||||||
qos: topic.qos,
|
qos: topic.qos,
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
this.subscribing$.next(false)
|
this.subscribing$.next(false)
|
||||||
this.addEvent({
|
this.addEvent({
|
||||||
time: Date.now(),
|
time: Date.now(),
|
||||||
|
|
|
||||||
|
|
@ -365,7 +365,7 @@ function makeVisitors(server, query, file, messages) {
|
||||||
infer.findRefs(cur.ast, cur.scope, name, scope, searchRef(cur))
|
infer.findRefs(cur.ast, cur.scope, name, scope, searchRef(cur))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (_e) {}
|
||||||
return hasRef
|
return hasRef
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ export function parseUrlAndPath(value) {
|
||||||
const url = new URL(value)
|
const url = new URL(value)
|
||||||
result.url = url.origin
|
result.url = url.origin
|
||||||
result.path = url.pathname
|
result.path = url.pathname
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
const uriRegex = value.match(
|
const uriRegex = value.match(
|
||||||
/^((http[s]?:\/\/)?(<<[^/]+>>)?[^/]*|)(\/?.*)$/
|
/^((http[s]?:\/\/)?(<<[^/]+>>)?[^/]*|)(\/?.*)$/
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -174,7 +174,7 @@ export function loadMockServers(skip?: number, take?: number) {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)()
|
)()
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
// Fallback to user mock servers if workspace service is not available
|
// Fallback to user mock servers if workspace service is not available
|
||||||
return pipe(
|
return pipe(
|
||||||
getMyMockServers(skip, take),
|
getMyMockServers(skip, take),
|
||||||
|
|
|
||||||
|
|
@ -289,8 +289,8 @@ export function applySetting<K extends keyof SettingsDef>(
|
||||||
) {
|
) {
|
||||||
settingsStore.dispatch({
|
settingsStore.dispatch({
|
||||||
dispatcher: "applySetting",
|
dispatcher: "applySetting",
|
||||||
|
// @ts-expect-error TS is not able to understand the type semantics here
|
||||||
payload: {
|
payload: {
|
||||||
// @ts-expect-error TS is not able to understand the type semantics here
|
|
||||||
settingKey,
|
settingKey,
|
||||||
value,
|
value,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -328,7 +328,7 @@ export class AgentInterceptorService extends Service implements Interceptor {
|
||||||
try {
|
try {
|
||||||
const proxyInfo = JSON.parse(persistedProxyInfo)
|
const proxyInfo = JSON.parse(persistedProxyInfo)
|
||||||
this.proxyInfo.value = proxyInfo
|
this.proxyInfo.value = proxyInfo
|
||||||
} catch (e) {}
|
} catch (_e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load SSL Validation
|
// Load SSL Validation
|
||||||
|
|
@ -557,7 +557,7 @@ export class AgentInterceptorService extends Service implements Interceptor {
|
||||||
try {
|
try {
|
||||||
await this.performHandshake()
|
await this.performHandshake()
|
||||||
this.isAgentRunning.value = true
|
this.isAgentRunning.value = true
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
this.isAgentRunning.value = false
|
this.isAgentRunning.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import { useSetting } from "~/composables/settings"
|
||||||
const isEncoded = (value: string) => {
|
const isEncoded = (value: string) => {
|
||||||
try {
|
try {
|
||||||
return value !== decodeURIComponent(value)
|
return value !== decodeURIComponent(value)
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
return false // in case of malformed URI sequence
|
return false // in case of malformed URI sequence
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -42,7 +42,7 @@ export const preProcessRequest = (
|
||||||
|
|
||||||
// decode the URL to prevent double encoding
|
// decode the URL to prevent double encoding
|
||||||
reqClone.url = decodeURIComponent(url.toString())
|
reqClone.url = decodeURIComponent(url.toString())
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
// making this a non-empty block, so we can make the linter happy.
|
// making this a non-empty block, so we can make the linter happy.
|
||||||
// we should probably use, allowEmptyCatch, or take the time to do something with the caught errors :)
|
// we should probably use, allowEmptyCatch, or take the time to do something with the caught errors :)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ describe("URLMenuService", () => {
|
||||||
try {
|
try {
|
||||||
new URL(url)
|
new URL(url)
|
||||||
return true
|
return true
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
// Fallback to regular expression check
|
// Fallback to regular expression check
|
||||||
const pattern = /^(https?:\/\/)?([\w.-]+)(\.[\w.-]+)+([/?].*)?$/
|
const pattern = /^(https?:\/\/)?([\w.-]+)(\.[\w.-]+)+([/?].*)?$/
|
||||||
return pattern.test(url)
|
return pattern.test(url)
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ function isValidURL(url: string) {
|
||||||
// this will fail for endpoints like "localhost:3000", ie without a protocol
|
// this will fail for endpoints like "localhost:3000", ie without a protocol
|
||||||
new URL(url)
|
new URL(url)
|
||||||
return true
|
return true
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
// Fallback to regular expression check
|
// Fallback to regular expression check
|
||||||
const pattern = /^(https?:\/\/)?([\w.-]+)(\.[\w.-]+)+([/?].*)?$/
|
const pattern = /^(https?:\/\/)?([\w.-]+)(\.[\w.-]+)+([/?].*)?$/
|
||||||
return pattern.test(url)
|
return pattern.test(url)
|
||||||
|
|
|
||||||
|
|
@ -185,7 +185,7 @@ const initAuthCodeOauthFlow = async ({
|
||||||
|
|
||||||
try {
|
try {
|
||||||
url = new URL(authEndpoint)
|
url = new URL(authEndpoint)
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
return E.left("INVALID_AUTH_ENDPOINT")
|
return E.left("INVALID_AUTH_ENDPOINT")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -354,7 +354,7 @@ export class PersistenceService extends Service {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
console.error(`Failed parsing persisted LOCAL_STATE:`, loadResult)
|
console.error(`Failed parsing persisted LOCAL_STATE:`, loadResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -386,7 +386,7 @@ export class PersistenceService extends Service {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
console.error(`Failed parsing persisted SETTINGS:`, loadResult)
|
console.error(`Failed parsing persisted SETTINGS:`, loadResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -418,7 +418,7 @@ export class PersistenceService extends Service {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
console.error(`Failed parsing persisted REST_HISTORY:`, restLoadResult)
|
console.error(`Failed parsing persisted REST_HISTORY:`, restLoadResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -450,7 +450,7 @@ export class PersistenceService extends Service {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
console.error(`Failed parsing persisted GQL_HISTORY:`, gqlLoadResult)
|
console.error(`Failed parsing persisted GQL_HISTORY:`, gqlLoadResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -486,7 +486,7 @@ export class PersistenceService extends Service {
|
||||||
setRESTCollections(data)
|
setRESTCollections(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
console.error(
|
console.error(
|
||||||
`Failed parsing persisted REST_COLLECTIONS:`,
|
`Failed parsing persisted REST_COLLECTIONS:`,
|
||||||
restLoadResult
|
restLoadResult
|
||||||
|
|
@ -523,7 +523,7 @@ export class PersistenceService extends Service {
|
||||||
setGraphqlCollections(data)
|
setGraphqlCollections(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
console.error(`Failed parsing persisted GQL_COLLECTIONS:`, gqlLoadResult)
|
console.error(`Failed parsing persisted GQL_COLLECTIONS:`, gqlLoadResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -569,7 +569,7 @@ export class PersistenceService extends Service {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
console.error(`Failed parsing persisted ENVIRONMENTS:`, loadResult)
|
console.error(`Failed parsing persisted ENVIRONMENTS:`, loadResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -607,7 +607,7 @@ export class PersistenceService extends Service {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
console.error(`Failed parsing persisted SECRET_ENVIRONMENTS:`, loadResult)
|
console.error(`Failed parsing persisted SECRET_ENVIRONMENTS:`, loadResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -653,7 +653,7 @@ export class PersistenceService extends Service {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
console.error(
|
console.error(
|
||||||
`Failed parsing persisted CURRENT_ENVIRONMENT_VALUE:`,
|
`Failed parsing persisted CURRENT_ENVIRONMENT_VALUE:`,
|
||||||
loadResult
|
loadResult
|
||||||
|
|
@ -699,7 +699,7 @@ export class PersistenceService extends Service {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
console.error(`Failed parsing persisted SELECTED_ENV:`, loadResult)
|
console.error(`Failed parsing persisted SELECTED_ENV:`, loadResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -735,7 +735,7 @@ export class PersistenceService extends Service {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
console.error(`Failed parsing persisted CURRENT_SORT_VALUES:`, loadResult)
|
console.error(`Failed parsing persisted CURRENT_SORT_VALUES:`, loadResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -780,7 +780,7 @@ export class PersistenceService extends Service {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
console.error(`Failed parsing persisted WEBSOCKET:`, loadResult)
|
console.error(`Failed parsing persisted WEBSOCKET:`, loadResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -817,7 +817,7 @@ export class PersistenceService extends Service {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
console.error(`Failed parsing persisted SOCKETIO:`, loadResult)
|
console.error(`Failed parsing persisted SOCKETIO:`, loadResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -847,7 +847,7 @@ export class PersistenceService extends Service {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
console.error(`Failed parsing persisted SSE:`, loadResult)
|
console.error(`Failed parsing persisted SSE:`, loadResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -877,7 +877,7 @@ export class PersistenceService extends Service {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
console.error(`Failed parsing persisted MQTT:`, loadResult)
|
console.error(`Failed parsing persisted MQTT:`, loadResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -908,7 +908,7 @@ export class PersistenceService extends Service {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
console.error(`Failed parsing persisted GLOBAL_ENV:`, loadResult)
|
console.error(`Failed parsing persisted GLOBAL_ENV:`, loadResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -955,7 +955,7 @@ export class PersistenceService extends Service {
|
||||||
this.restTabService.loadTabsFromPersistedState(loadResult.right)
|
this.restTabService.loadTabsFromPersistedState(loadResult.right)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
console.error(`Failed parsing persisted REST_TABS:`, loadResult)
|
console.error(`Failed parsing persisted REST_TABS:`, loadResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -998,7 +998,7 @@ export class PersistenceService extends Service {
|
||||||
this.gqlTabService.loadTabsFromPersistedState(loadResult.right)
|
this.gqlTabService.loadTabsFromPersistedState(loadResult.right)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
console.error(`Failed parsing persisted GQL_TABS:`, loadResult)
|
console.error(`Failed parsing persisted GQL_TABS:`, loadResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -119,7 +119,7 @@ export class CollectionsSpotlightSearcherService
|
||||||
return "rest"
|
return "rest"
|
||||||
}
|
}
|
||||||
return "other"
|
return "other"
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
return "other"
|
return "other"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ export class TeamsSpotlightSearcherService
|
||||||
return "rest"
|
return "rest"
|
||||||
}
|
}
|
||||||
return "other"
|
return "other"
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
return "other"
|
return "other"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -194,7 +194,7 @@ export class TestRunnerService extends Service {
|
||||||
if (options.delay && options.delay > 0) {
|
if (options.delay && options.delay > 0) {
|
||||||
try {
|
try {
|
||||||
await delay(options.delay)
|
await delay(options.delay)
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
if (options.stopRef?.value) {
|
if (options.stopRef?.value) {
|
||||||
tab.value.document.status = "stopped"
|
tab.value.document.status = "stopped"
|
||||||
throw new Error("Test execution stopped")
|
throw new Error("Test execution stopped")
|
||||||
|
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
/* eslint-env node */
|
|
||||||
require("@rushstack/eslint-patch/modern-module-resolution")
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
env: {
|
|
||||||
browser: true,
|
|
||||||
node: true,
|
|
||||||
jest: true,
|
|
||||||
},
|
|
||||||
parserOptions: {
|
|
||||||
sourceType: "module",
|
|
||||||
requireConfigFile: false,
|
|
||||||
ecmaFeatures: {
|
|
||||||
jsx: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
extends: [
|
|
||||||
"@vue/typescript/recommended",
|
|
||||||
"plugin:vue/recommended",
|
|
||||||
"plugin:prettier/recommended",
|
|
||||||
],
|
|
||||||
ignorePatterns: [
|
|
||||||
"static/**/*",
|
|
||||||
"./helpers/backend/graphql.ts",
|
|
||||||
"**/*.d.ts",
|
|
||||||
"types/**/*",
|
|
||||||
],
|
|
||||||
plugins: ["vue", "prettier"],
|
|
||||||
// add your custom rules here
|
|
||||||
rules: {
|
|
||||||
semi: [2, "never"],
|
|
||||||
"import/named": "off", // because, named import issue with typescript see: https://github.com/typescript-eslint/typescript-eslint/issues/154
|
|
||||||
"no-console": "off",
|
|
||||||
"no-debugger": process.env.HOPP_LINT_FOR_PROD === "true" ? "error" : "warn",
|
|
||||||
"prettier/prettier":
|
|
||||||
process.env.HOPP_LINT_FOR_PROD === "true" ? "error" : "warn",
|
|
||||||
"vue/multi-word-component-names": "off",
|
|
||||||
"vue/no-side-effects-in-computed-properties": "off",
|
|
||||||
"import/no-named-as-default": "off",
|
|
||||||
"import/no-named-as-default-member": "off",
|
|
||||||
"@typescript-eslint/no-unused-vars":
|
|
||||||
process.env.HOPP_LINT_FOR_PROD === "true" ? "error" : "warn",
|
|
||||||
"@typescript-eslint/no-non-null-assertion": "off",
|
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
|
||||||
"import/default": "off",
|
|
||||||
"no-undef": "off",
|
|
||||||
// localStorage block
|
|
||||||
"no-restricted-globals": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
name: "localStorage",
|
|
||||||
message:
|
|
||||||
"Do not use 'localStorage' directly. Please use localpersistence.ts functions or stores",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
// window.localStorage block
|
|
||||||
"no-restricted-syntax": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
selector: "CallExpression[callee.object.property.name='localStorage']",
|
|
||||||
message:
|
|
||||||
"Do not use 'localStorage' directly. Please use localpersistence.ts functions or stores",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
79
packages/hoppscotch-desktop/eslint.config.mjs
Normal file
79
packages/hoppscotch-desktop/eslint.config.mjs
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
import pluginVue from "eslint-plugin-vue"
|
||||||
|
import {
|
||||||
|
defineConfigWithVueTs,
|
||||||
|
vueTsConfigs,
|
||||||
|
} from "@vue/eslint-config-typescript"
|
||||||
|
import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended"
|
||||||
|
import globals from "globals"
|
||||||
|
|
||||||
|
export default defineConfigWithVueTs(
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
"static/**",
|
||||||
|
"src/helpers/backend/graphql.ts",
|
||||||
|
"**/*.d.ts",
|
||||||
|
"types/**",
|
||||||
|
"dist/**",
|
||||||
|
"node_modules/**",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
pluginVue.configs["flat/recommended"],
|
||||||
|
vueTsConfigs.recommended,
|
||||||
|
eslintPluginPrettierRecommended,
|
||||||
|
{
|
||||||
|
files: ["**/*.ts", "**/*.js", "**/*.vue"],
|
||||||
|
linterOptions: {
|
||||||
|
reportUnusedDisableDirectives: false,
|
||||||
|
},
|
||||||
|
languageOptions: {
|
||||||
|
sourceType: "module",
|
||||||
|
ecmaVersion: "latest",
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.node,
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
requireConfigFile: false,
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
semi: [2, "never"],
|
||||||
|
"import/named": "off",
|
||||||
|
"no-console": "off",
|
||||||
|
"no-debugger": process.env.HOPP_LINT_FOR_PROD === "true" ? "error" : "warn",
|
||||||
|
"prettier/prettier":
|
||||||
|
process.env.HOPP_LINT_FOR_PROD === "true" ? "error" : "warn",
|
||||||
|
"vue/multi-word-component-names": "off",
|
||||||
|
"vue/no-side-effects-in-computed-properties": "off",
|
||||||
|
"import/no-named-as-default": "off",
|
||||||
|
"import/no-named-as-default-member": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars":
|
||||||
|
process.env.HOPP_LINT_FOR_PROD === "true" ? "error" : "warn",
|
||||||
|
"@typescript-eslint/no-unused-expressions": "off",
|
||||||
|
"@typescript-eslint/no-non-null-assertion": "off",
|
||||||
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-function-type": "off",
|
||||||
|
"import/default": "off",
|
||||||
|
"no-undef": "off",
|
||||||
|
"no-restricted-globals": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
name: "localStorage",
|
||||||
|
message:
|
||||||
|
"Do not use 'localStorage' directly. Please use the PersistenceService",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"no-restricted-syntax": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
selector: "CallExpression[callee.object.property.name='localStorage']",
|
||||||
|
message:
|
||||||
|
"Do not use 'localStorage' directly. Please use the PersistenceService",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
@ -8,10 +8,13 @@
|
||||||
"build": "vue-tsc --noEmit && vite build",
|
"build": "vue-tsc --noEmit && vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"tauri": "tauri",
|
"tauri": "tauri",
|
||||||
"lint": "eslint src --ext .ts,.js,.vue --ignore-path .gitignore .",
|
"lint": "eslint src",
|
||||||
"lint:ts": "vue-tsc --noEmit",
|
"lint:ts": "vue-tsc --noEmit",
|
||||||
"lintfix": "eslint --fix src --ext .ts,.js,.vue --ignore-path .gitignore .",
|
"lintfix": "eslint --fix src",
|
||||||
"prod-lint": "cross-env HOPP_LINT_FOR_PROD=true pnpm run lint",
|
"prod-lint": "cross-env HOPP_LINT_FOR_PROD=true pnpm run lint",
|
||||||
|
"do-lint": "pnpm run prod-lint",
|
||||||
|
"do-typecheck": "pnpm run lint:ts",
|
||||||
|
"do-lintfix": "pnpm run lintfix",
|
||||||
"prepare-web": "(cd ../hoppscotch-selfhost-web && pnpm install && pnpm generate) && (cd crates/webapp-bundler && cargo build --release && cd target/release && ./webapp-bundler --input ../../../../../hoppscotch-selfhost-web/dist --output ../../../../bundle.zip --manifest ../../../../manifest.json)",
|
"prepare-web": "(cd ../hoppscotch-selfhost-web && pnpm install && pnpm generate) && (cd crates/webapp-bundler && cargo build --release && cd target/release && ./webapp-bundler --input ../../../../../hoppscotch-selfhost-web/dist --output ../../../../bundle.zip --manifest ../../../../manifest.json)",
|
||||||
"dev:full": "pnpm tauri dev",
|
"dev:full": "pnpm tauri dev",
|
||||||
"build:full": "pnpm tauri build",
|
"build:full": "pnpm tauri build",
|
||||||
|
|
@ -20,7 +23,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource-variable/inter": "5.2.8",
|
"@fontsource-variable/inter": "5.2.8",
|
||||||
"@fontsource-variable/material-symbols-rounded": "5.2.24",
|
"@fontsource-variable/material-symbols-rounded": "5.2.30",
|
||||||
"@fontsource-variable/roboto-mono": "5.2.8",
|
"@fontsource-variable/roboto-mono": "5.2.8",
|
||||||
"@hoppscotch/common": "workspace:^",
|
"@hoppscotch/common": "workspace:^",
|
||||||
"@hoppscotch/kernel": "workspace:^",
|
"@hoppscotch/kernel": "workspace:^",
|
||||||
|
|
@ -29,36 +32,38 @@
|
||||||
"@tauri-apps/api": "2.1.1",
|
"@tauri-apps/api": "2.1.1",
|
||||||
"@tauri-apps/plugin-fs": "2.0.2",
|
"@tauri-apps/plugin-fs": "2.0.2",
|
||||||
"@tauri-apps/plugin-process": "2.2.0",
|
"@tauri-apps/plugin-process": "2.2.0",
|
||||||
"@tauri-apps/plugin-shell": "2.2.1",
|
"@tauri-apps/plugin-shell": "2.3.3",
|
||||||
"@tauri-apps/plugin-store": "2.2.0",
|
"@tauri-apps/plugin-store": "2.4.1",
|
||||||
"@tauri-apps/plugin-updater": "2.5.1",
|
"@tauri-apps/plugin-updater": "2.9.0",
|
||||||
"@vueuse/core": "13.7.0",
|
|
||||||
"fp-ts": "2.16.11",
|
"fp-ts": "2.16.11",
|
||||||
"rxjs": "7.8.2",
|
"rxjs": "7.8.2",
|
||||||
"vue": "3.5.22",
|
"vue": "3.5.26",
|
||||||
"vue-router": "4.6.3",
|
"vue-router": "4.6.4",
|
||||||
"vue-tippy": "6.7.1",
|
"vue-tippy": "6.7.1",
|
||||||
"zod": "3.25.32"
|
"zod": "3.25.32"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@iconify-json/lucide": "1.2.68",
|
"@eslint/eslintrc": "3.3.3",
|
||||||
"@rushstack/eslint-patch": "1.14.0",
|
"@eslint/js": "9.39.2",
|
||||||
"@tauri-apps/cli": "^2",
|
"@iconify-json/lucide": "1.2.81",
|
||||||
"@typescript-eslint/eslint-plugin": "8.44.1",
|
"@rushstack/eslint-patch": "1.15.0",
|
||||||
"@typescript-eslint/parser": "8.44.1",
|
"@tauri-apps/cli": "2.9.3",
|
||||||
"@vitejs/plugin-vue": "5.1.4",
|
"@typescript-eslint/eslint-plugin": "8.50.0",
|
||||||
"@vue/eslint-config-typescript": "13.0.0",
|
"@typescript-eslint/parser": "8.50.0",
|
||||||
"autoprefixer": "10.4.21",
|
"@vitejs/plugin-vue": "6.0.3",
|
||||||
"eslint": "8.57.0",
|
"@vue/eslint-config-typescript": "14.6.0",
|
||||||
|
"autoprefixer": "10.4.23",
|
||||||
|
"eslint": "9.39.2",
|
||||||
"eslint-plugin-prettier": "5.5.4",
|
"eslint-plugin-prettier": "5.5.4",
|
||||||
"eslint-plugin-vue": "10.5.1",
|
"eslint-plugin-vue": "10.6.2",
|
||||||
|
"globals": "16.5.0",
|
||||||
"postcss": "8.5.6",
|
"postcss": "8.5.6",
|
||||||
"sass": "1.93.2",
|
"sass": "1.97.0",
|
||||||
"tailwindcss": "3.4.16",
|
"tailwindcss": "3.4.16",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
"unplugin-icons": "22.2.0",
|
"unplugin-icons": "22.5.0",
|
||||||
"unplugin-vue-components": "29.0.0",
|
"unplugin-vue-components": "30.0.0",
|
||||||
"vite": "6.3.5",
|
"vite": "7.3.0",
|
||||||
"vue-tsc": "2.2.0"
|
"vue-tsc": "2.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
|
// biome-ignore lint: disable
|
||||||
|
// oxlint-disable
|
||||||
|
// ------
|
||||||
// Generated by unplugin-vue-components
|
// Generated by unplugin-vue-components
|
||||||
// Read more: https://github.com/vuejs/core/pull/3399
|
// Read more: https://github.com/vuejs/core/pull/3399
|
||||||
// biome-ignore lint: disable
|
|
||||||
export {}
|
export {}
|
||||||
|
|
||||||
/* prettier-ignore */
|
/* prettier-ignore */
|
||||||
|
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
env: {
|
|
||||||
node: true,
|
|
||||||
jest: true,
|
|
||||||
browser: true,
|
|
||||||
},
|
|
||||||
parser: "@typescript-eslint/parser",
|
|
||||||
parserOptions: {
|
|
||||||
sourceType: "module",
|
|
||||||
requireConfigFile: false,
|
|
||||||
ecmaVersion: 2021,
|
|
||||||
},
|
|
||||||
plugins: ["prettier"],
|
|
||||||
extends: [
|
|
||||||
"prettier/prettier",
|
|
||||||
"eslint:recommended",
|
|
||||||
"plugin:prettier/recommended",
|
|
||||||
"plugin:@typescript-eslint/eslint-recommended",
|
|
||||||
"plugin:@typescript-eslint/recommended",
|
|
||||||
],
|
|
||||||
rules: {
|
|
||||||
semi: [2, "never"],
|
|
||||||
"prettier/prettier": ["warn", { semi: false, trailingComma: "es5" }],
|
|
||||||
"import/no-named-as-default": "off",
|
|
||||||
"no-undef": "off",
|
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
|
||||||
"@typescript-eslint/no-non-null-assertion": "off",
|
|
||||||
"@typescript-eslint/no-unused-vars": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
args: "all",
|
|
||||||
argsIgnorePattern: "^_",
|
|
||||||
caughtErrors: "all",
|
|
||||||
caughtErrorsIgnorePattern: "^_",
|
|
||||||
destructuredArrayIgnorePattern: "^_",
|
|
||||||
varsIgnorePattern: "^_",
|
|
||||||
ignoreRestSiblings: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
70
packages/hoppscotch-js-sandbox/eslint.config.cjs
Normal file
70
packages/hoppscotch-js-sandbox/eslint.config.cjs
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
const { FlatCompat } = require("@eslint/eslintrc")
|
||||||
|
const js = require("@eslint/js")
|
||||||
|
const tsParser = require("@typescript-eslint/parser")
|
||||||
|
const typescriptEslintPlugin = require("@typescript-eslint/eslint-plugin")
|
||||||
|
const prettierPlugin = require("eslint-plugin-prettier")
|
||||||
|
const globals = require("globals")
|
||||||
|
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: __dirname,
|
||||||
|
recommendedConfig: js.configs.recommended,
|
||||||
|
allConfig: js.configs.all,
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
"dist/**",
|
||||||
|
"node_modules/**",
|
||||||
|
"**/*.d.ts",
|
||||||
|
"eslint.config.cjs",
|
||||||
|
".prettierrc.cjs",
|
||||||
|
"src/bootstrap-code/**",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
...compat.extends(
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:prettier/recommended",
|
||||||
|
"plugin:@typescript-eslint/eslint-recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended"
|
||||||
|
),
|
||||||
|
{
|
||||||
|
files: ["**/*.ts", "**/*.js"],
|
||||||
|
linterOptions: {
|
||||||
|
reportUnusedDisableDirectives: false,
|
||||||
|
},
|
||||||
|
languageOptions: {
|
||||||
|
parser: tsParser,
|
||||||
|
sourceType: "module",
|
||||||
|
ecmaVersion: 2021,
|
||||||
|
globals: {
|
||||||
|
...globals.node,
|
||||||
|
...globals.jest,
|
||||||
|
...globals.browser,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
"@typescript-eslint": typescriptEslintPlugin,
|
||||||
|
prettier: prettierPlugin,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
semi: [2, "never"],
|
||||||
|
"prettier/prettier": ["warn", { semi: false, trailingComma: "es5" }],
|
||||||
|
"no-undef": "off",
|
||||||
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
"@typescript-eslint/no-non-null-assertion": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
args: "all",
|
||||||
|
argsIgnorePattern: "^_",
|
||||||
|
caughtErrors: "all",
|
||||||
|
caughtErrorsIgnorePattern: "^_",
|
||||||
|
destructuredArrayIgnorePattern: "^_",
|
||||||
|
varsIgnorePattern: "^_",
|
||||||
|
ignoreRestSiblings: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
@ -27,8 +27,8 @@
|
||||||
"node": ">=22"
|
"node": ">=22"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint --ext .ts,.js --ignore-path .gitignore .",
|
"lint": "eslint .",
|
||||||
"lintfix": "eslint --fix --ext .ts,.js --ignore-path .gitignore .",
|
"lintfix": "eslint --fix .",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"build": "vite build && tsc --emitDeclarationOnly",
|
"build": "vite build && tsc --emitDeclarationOnly",
|
||||||
"clean": "pnpm tsc --build --clean",
|
"clean": "pnpm tsc --build --clean",
|
||||||
|
|
@ -67,7 +67,10 @@
|
||||||
"@types/node": "24.10.1",
|
"@types/node": "24.10.1",
|
||||||
"@typescript-eslint/eslint-plugin": "8.50.0",
|
"@typescript-eslint/eslint-plugin": "8.50.0",
|
||||||
"@typescript-eslint/parser": "8.50.0",
|
"@typescript-eslint/parser": "8.50.0",
|
||||||
"eslint": "8.57.0",
|
"@eslint/eslintrc": "3.3.3",
|
||||||
|
"@eslint/js": "9.39.2",
|
||||||
|
"eslint": "9.39.2",
|
||||||
|
"globals": "16.5.0",
|
||||||
"eslint-config-prettier": "10.1.8",
|
"eslint-config-prettier": "10.1.8",
|
||||||
"eslint-plugin-prettier": "5.5.4",
|
"eslint-plugin-prettier": "5.5.4",
|
||||||
"io-ts": "2.2.22",
|
"io-ts": "2.2.22",
|
||||||
|
|
|
||||||
52
packages/hoppscotch-kernel/eslint.config.mjs
Normal file
52
packages/hoppscotch-kernel/eslint.config.mjs
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
import js from "@eslint/js"
|
||||||
|
import tsParser from "@typescript-eslint/parser"
|
||||||
|
import typescriptEslintPlugin from "@typescript-eslint/eslint-plugin"
|
||||||
|
import prettierPlugin from "eslint-plugin-prettier"
|
||||||
|
import globals from "globals"
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
"dist/**",
|
||||||
|
"node_modules/**",
|
||||||
|
"**/*.d.ts",
|
||||||
|
"vite.config.ts",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
js.configs.recommended,
|
||||||
|
{
|
||||||
|
files: ["src/**/*.ts"],
|
||||||
|
languageOptions: {
|
||||||
|
parser: tsParser,
|
||||||
|
sourceType: "module",
|
||||||
|
ecmaVersion: 2022,
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.node,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
"@typescript-eslint": typescriptEslintPlugin,
|
||||||
|
prettier: prettierPlugin,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...typescriptEslintPlugin.configs.recommended.rules,
|
||||||
|
"prettier/prettier": "warn",
|
||||||
|
"no-undef": "off",
|
||||||
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
"@typescript-eslint/no-non-null-assertion": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
args: "all",
|
||||||
|
argsIgnorePattern: "^_",
|
||||||
|
caughtErrors: "all",
|
||||||
|
caughtErrorsIgnorePattern: "^_",
|
||||||
|
destructuredArrayIgnorePattern: "^_",
|
||||||
|
varsIgnorePattern: "^_",
|
||||||
|
ignoreRestSiblings: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
@ -15,7 +15,11 @@
|
||||||
"build:decl": "tsc --project tsconfig.decl.json",
|
"build:decl": "tsc --project tsconfig.decl.json",
|
||||||
"build": "pnpm run build:code && pnpm run build:decl",
|
"build": "pnpm run build:code && pnpm run build:decl",
|
||||||
"prepare": "pnpm run build:code && pnpm run build:decl",
|
"prepare": "pnpm run build:code && pnpm run build:decl",
|
||||||
"do-typecheck": "pnpm exec tsc --noEmit"
|
"do-typecheck": "pnpm exec tsc --noEmit",
|
||||||
|
"lint": "eslint src",
|
||||||
|
"lintfix": "eslint --fix src",
|
||||||
|
"do-lint": "pnpm run lint",
|
||||||
|
"do-lintfix": "pnpm run lintfix"
|
||||||
},
|
},
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
|
|
@ -35,7 +39,13 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/hoppscotch/hoppscotch#readme",
|
"homepage": "https://github.com/hoppscotch/hoppscotch#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eslint/js": "9.39.2",
|
||||||
"@types/node": "24.9.1",
|
"@types/node": "24.9.1",
|
||||||
|
"@typescript-eslint/eslint-plugin": "8.50.0",
|
||||||
|
"@typescript-eslint/parser": "8.50.0",
|
||||||
|
"eslint": "9.39.2",
|
||||||
|
"eslint-plugin-prettier": "5.5.4",
|
||||||
|
"globals": "16.5.0",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
"vite": "7.3.0"
|
"vite": "7.3.0"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
import type { Version } from './type/versioning'
|
import type { Version } from "./type/versioning"
|
||||||
|
|
||||||
import { VERSIONS as IO_VERSIONS } from './io'
|
import { VERSIONS as IO_VERSIONS } from "./io"
|
||||||
import { IO_IMPLS as WEB_IO_IMPLS } from './io/impl/web'
|
import { IO_IMPLS as WEB_IO_IMPLS } from "./io/impl/web"
|
||||||
import { IO_IMPLS as DESKTOP_IO_IMPLS } from './io/impl/desktop'
|
import { IO_IMPLS as DESKTOP_IO_IMPLS } from "./io/impl/desktop"
|
||||||
|
|
||||||
import { VERSIONS as RELAY_VERSIONS } from './relay'
|
import { VERSIONS as RELAY_VERSIONS } from "./relay"
|
||||||
import { RELAY_IMPLS as WEB_RELAY_IMPLS } from './relay/impl/web'
|
import { RELAY_IMPLS as WEB_RELAY_IMPLS } from "./relay/impl/web"
|
||||||
import { RELAY_IMPLS as DESKTOP_RELAY_IMPLS } from './relay/impl/desktop'
|
import { RELAY_IMPLS as DESKTOP_RELAY_IMPLS } from "./relay/impl/desktop"
|
||||||
|
|
||||||
import { VERSIONS as STORE_VERSIONS } from './store'
|
import { VERSIONS as STORE_VERSIONS } from "./store"
|
||||||
import { STORE_IMPLS as WEB_STORE_IMPLS } from './store/impl/web'
|
import { STORE_IMPLS as WEB_STORE_IMPLS } from "./store/impl/web"
|
||||||
import { STORE_IMPLS as DESKTOP_STORE_IMPLS } from './store/impl/desktop'
|
import { STORE_IMPLS as DESKTOP_STORE_IMPLS } from "./store/impl/desktop"
|
||||||
|
|
||||||
export interface KernelInfo {
|
export interface KernelInfo {
|
||||||
name: string
|
name: string
|
||||||
|
|
@ -25,7 +25,7 @@ export interface KernelAPI {
|
||||||
store: typeof STORE_VERSIONS.v1.api
|
store: typeof STORE_VERSIONS.v1.api
|
||||||
}
|
}
|
||||||
|
|
||||||
export type KernelMode = 'web' | 'desktop'
|
export type KernelMode = "web" | "desktop"
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
|
|
@ -39,16 +39,16 @@ export function getKernelMode(): KernelMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initKernel(mode?: KernelMode): KernelAPI {
|
export function initKernel(mode?: KernelMode): KernelAPI {
|
||||||
if (mode === 'desktop') {
|
if (mode === "desktop") {
|
||||||
const kernel: KernelAPI = {
|
const kernel: KernelAPI = {
|
||||||
info: {
|
info: {
|
||||||
name: "desktop-kernel",
|
name: "desktop-kernel",
|
||||||
version: { major: 1, minor: 0, patch: 0 },
|
version: { major: 1, minor: 0, patch: 0 },
|
||||||
capabilities: ["basic-io"]
|
capabilities: ["basic-io"],
|
||||||
},
|
},
|
||||||
io: DESKTOP_IO_IMPLS.v1.api,
|
io: DESKTOP_IO_IMPLS.v1.api,
|
||||||
relay: DESKTOP_RELAY_IMPLS.v1.api,
|
relay: DESKTOP_RELAY_IMPLS.v1.api,
|
||||||
store: DESKTOP_STORE_IMPLS.v1.api
|
store: DESKTOP_STORE_IMPLS.v1.api,
|
||||||
}
|
}
|
||||||
|
|
||||||
window.__KERNEL__ = kernel
|
window.__KERNEL__ = kernel
|
||||||
|
|
@ -58,11 +58,11 @@ export function initKernel(mode?: KernelMode): KernelAPI {
|
||||||
info: {
|
info: {
|
||||||
name: "web-kernel",
|
name: "web-kernel",
|
||||||
version: { major: 1, minor: 0, patch: 0 },
|
version: { major: 1, minor: 0, patch: 0 },
|
||||||
capabilities: ["basic-io"]
|
capabilities: ["basic-io"],
|
||||||
},
|
},
|
||||||
io: WEB_IO_IMPLS.v1.api,
|
io: WEB_IO_IMPLS.v1.api,
|
||||||
relay: WEB_RELAY_IMPLS.v1.api,
|
relay: WEB_RELAY_IMPLS.v1.api,
|
||||||
store: WEB_STORE_IMPLS.v1.api
|
store: WEB_STORE_IMPLS.v1.api,
|
||||||
}
|
}
|
||||||
|
|
||||||
window.__KERNEL__ = kernel
|
window.__KERNEL__ = kernel
|
||||||
|
|
@ -79,7 +79,7 @@ export type {
|
||||||
EventCallback,
|
EventCallback,
|
||||||
UnlistenFn,
|
UnlistenFn,
|
||||||
IoV1,
|
IoV1,
|
||||||
} from '@io/v/1'
|
} from "@io/v/1"
|
||||||
|
|
||||||
export type {
|
export type {
|
||||||
RelayRequest,
|
RelayRequest,
|
||||||
|
|
@ -98,15 +98,15 @@ export type {
|
||||||
RelayCapabilities,
|
RelayCapabilities,
|
||||||
RelayEventEmitter,
|
RelayEventEmitter,
|
||||||
RelayRequestEvents,
|
RelayRequestEvents,
|
||||||
StatusCode
|
StatusCode,
|
||||||
} from '@relay/v/1'
|
} from "@relay/v/1"
|
||||||
|
|
||||||
export {
|
export {
|
||||||
content,
|
content,
|
||||||
body,
|
body,
|
||||||
MediaType,
|
MediaType,
|
||||||
relayRequestToNativeAdapter
|
relayRequestToNativeAdapter,
|
||||||
} from '@relay/v/1'
|
} from "@relay/v/1"
|
||||||
|
|
||||||
export type {
|
export type {
|
||||||
StoreCapability,
|
StoreCapability,
|
||||||
|
|
@ -120,4 +120,4 @@ export type {
|
||||||
StoredData,
|
StoredData,
|
||||||
StoreEventEmitter,
|
StoreEventEmitter,
|
||||||
StoreV1,
|
StoreV1,
|
||||||
} from '@store/v/1'
|
} from "@store/v/1"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { implementation as ioV1 } from './v/1'
|
import { implementation as ioV1 } from "./v/1"
|
||||||
|
|
||||||
export const IO_IMPLS = {
|
export const IO_IMPLS = {
|
||||||
v1: ioV1,
|
v1: ioV1,
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,18 @@
|
||||||
import { VersionedAPI } from '@type/versioning'
|
import { VersionedAPI } from "@type/versioning"
|
||||||
import { IoV1, SaveFileWithDialogOptions, OpenExternalLinkOptions } from '@io/v/1'
|
import {
|
||||||
|
IoV1,
|
||||||
|
SaveFileWithDialogOptions,
|
||||||
|
OpenExternalLinkOptions,
|
||||||
|
} from "@io/v/1"
|
||||||
|
|
||||||
import { save } from "@tauri-apps/plugin-dialog"
|
import { save } from "@tauri-apps/plugin-dialog"
|
||||||
import { writeFile, writeTextFile } from "@tauri-apps/plugin-fs"
|
import { writeFile, writeTextFile } from "@tauri-apps/plugin-fs"
|
||||||
import { open } from "@tauri-apps/plugin-shell"
|
import { open } from "@tauri-apps/plugin-shell"
|
||||||
import { listen as tauriListen, emit as tauriEmit, Event } from '@tauri-apps/api/event'
|
import {
|
||||||
|
listen as tauriListen,
|
||||||
|
emit as tauriEmit,
|
||||||
|
Event,
|
||||||
|
} from "@tauri-apps/api/event"
|
||||||
|
|
||||||
export const implementation: VersionedAPI<IoV1> = {
|
export const implementation: VersionedAPI<IoV1> = {
|
||||||
version: { major: 1, minor: 0, patch: 0 },
|
version: { major: 1, minor: 0, patch: 0 },
|
||||||
|
|
@ -47,6 +55,6 @@ export const implementation: VersionedAPI<IoV1> = {
|
||||||
|
|
||||||
async emit(event: string, payload?: unknown) {
|
async emit(event: string, payload?: unknown) {
|
||||||
await tauriEmit(event, payload)
|
await tauriEmit(event, payload)
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { implementation as ioV1 } from './v/1'
|
import { implementation as ioV1 } from "./v/1"
|
||||||
|
|
||||||
export const IO_IMPLS = {
|
export const IO_IMPLS = {
|
||||||
v1: ioV1,
|
v1: ioV1,
|
||||||
|
|
|
||||||
|
|
@ -1,90 +1,97 @@
|
||||||
import { VersionedAPI } from '@type/versioning'
|
import { VersionedAPI } from "@type/versioning"
|
||||||
import { IoV1, SaveFileWithDialogOptions, OpenExternalLinkOptions, Event } from '@io/v/1'
|
import {
|
||||||
import { pipe } from 'fp-ts/function'
|
IoV1,
|
||||||
import * as S from 'fp-ts/string'
|
SaveFileWithDialogOptions,
|
||||||
import * as RNEA from 'fp-ts/ReadonlyNonEmptyArray'
|
OpenExternalLinkOptions,
|
||||||
|
Event,
|
||||||
|
} from "@io/v/1"
|
||||||
|
import { pipe } from "fp-ts/function"
|
||||||
|
import * as S from "fp-ts/string"
|
||||||
|
import * as RNEA from "fp-ts/ReadonlyNonEmptyArray"
|
||||||
|
|
||||||
export const implementation: VersionedAPI<IoV1> = {
|
export const implementation: VersionedAPI<IoV1> = {
|
||||||
version: { major: 1, minor: 0, patch: 0 },
|
version: { major: 1, minor: 0, patch: 0 },
|
||||||
api: {
|
api: {
|
||||||
async saveFileWithDialog(opts: SaveFileWithDialogOptions) {
|
async saveFileWithDialog(opts: SaveFileWithDialogOptions) {
|
||||||
// TODO: Revisit this because perhaps a better approach is
|
// TODO: Revisit this because perhaps a better approach is
|
||||||
// ```ts
|
// ```ts
|
||||||
// const data: BlobPart = typeof opts.data === 'string'
|
// const data: BlobPart = typeof opts.data === 'string'
|
||||||
// ? opts.data
|
// ? opts.data
|
||||||
// : new Uint8Array(opts.data);
|
// : new Uint8Array(opts.data);
|
||||||
// const file = new Blob([data], { type: opts.contentType })
|
// const file = new Blob([data], { type: opts.contentType })
|
||||||
// ```
|
// ```
|
||||||
const file = new Blob([opts.data as BlobPart], { type: opts.contentType })
|
const file = new Blob([opts.data as BlobPart], { type: opts.contentType })
|
||||||
const a = document.createElement("a")
|
const a = document.createElement("a")
|
||||||
const url = URL.createObjectURL(file)
|
const url = URL.createObjectURL(file)
|
||||||
|
|
||||||
a.href = url
|
a.href = url
|
||||||
a.download = opts.suggestedFilename ?? pipe(
|
a.download =
|
||||||
url,
|
opts.suggestedFilename ??
|
||||||
S.split("/"),
|
pipe(
|
||||||
RNEA.last,
|
url,
|
||||||
S.split("#"),
|
S.split("/"),
|
||||||
RNEA.head,
|
RNEA.last,
|
||||||
S.split("?"),
|
S.split("#"),
|
||||||
RNEA.head
|
RNEA.head,
|
||||||
)
|
S.split("?"),
|
||||||
|
RNEA.head
|
||||||
|
)
|
||||||
|
|
||||||
document.body.appendChild(a)
|
document.body.appendChild(a)
|
||||||
a.click()
|
a.click()
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
document.body.removeChild(a)
|
document.body.removeChild(a)
|
||||||
URL.revokeObjectURL(url)
|
URL.revokeObjectURL(url)
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
|
||||||
// Browsers provide no way to know if save was successful
|
// Browsers provide no way to know if save was successful
|
||||||
return { type: "unknown" as const }
|
return { type: "unknown" as const }
|
||||||
},
|
},
|
||||||
|
|
||||||
async openExternalLink(opts: OpenExternalLinkOptions) {
|
async openExternalLink(opts: OpenExternalLinkOptions) {
|
||||||
window.open(opts.url, "_blank")
|
window.open(opts.url, "_blank")
|
||||||
// Browsers provide no way to know if save was successful
|
// Browsers provide no way to know if save was successful
|
||||||
return { type: "unknown" as const }
|
return { type: "unknown" as const }
|
||||||
},
|
},
|
||||||
|
|
||||||
async listen<T>(event: string, handler: (event: Event<T>) => void) {
|
async listen<T>(event: string, handler: (event: Event<T>) => void) {
|
||||||
const listener = (e: HashChangeEvent) => {
|
const listener = (_e: HashChangeEvent) => {
|
||||||
const hash = window.location.hash
|
const hash = window.location.hash
|
||||||
if (hash && hash.startsWith(`#${event}:`)) {
|
if (hash && hash.startsWith(`#${event}:`)) {
|
||||||
const payload = hash.slice(event.length + 2) // Remove #event:
|
const payload = hash.slice(event.length + 2) // Remove #event:
|
||||||
handler({
|
handler({
|
||||||
event,
|
event,
|
||||||
id: Date.now(),
|
id: Date.now(),
|
||||||
payload: JSON.parse(payload) as T
|
payload: JSON.parse(payload) as T,
|
||||||
})
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('hashchange', listener)
|
|
||||||
return () => window.removeEventListener('hashchange', listener)
|
|
||||||
},
|
|
||||||
|
|
||||||
async once<T>(event: string, handler: (event: Event<T>) => void) {
|
|
||||||
const listener = (e: HashChangeEvent) => {
|
|
||||||
const hash = window.location.hash
|
|
||||||
if (hash && hash.startsWith(`#${event}:`)) {
|
|
||||||
const payload = hash.slice(event.length + 2) // Remove #event:
|
|
||||||
window.removeEventListener('hashchange', listener)
|
|
||||||
handler({
|
|
||||||
event,
|
|
||||||
id: Date.now(),
|
|
||||||
payload: JSON.parse(payload) as T
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('hashchange', listener)
|
|
||||||
return () => window.removeEventListener('hashchange', listener)
|
|
||||||
},
|
|
||||||
|
|
||||||
async emit(event: string, payload?: unknown) {
|
|
||||||
window.location.hash = `${event}:${JSON.stringify(payload)}`
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.addEventListener("hashchange", listener)
|
||||||
|
return () => window.removeEventListener("hashchange", listener)
|
||||||
|
},
|
||||||
|
|
||||||
|
async once<T>(event: string, handler: (event: Event<T>) => void) {
|
||||||
|
const listener = (_e: HashChangeEvent) => {
|
||||||
|
const hash = window.location.hash
|
||||||
|
if (hash && hash.startsWith(`#${event}:`)) {
|
||||||
|
const payload = hash.slice(event.length + 2) // Remove #event:
|
||||||
|
window.removeEventListener("hashchange", listener)
|
||||||
|
handler({
|
||||||
|
event,
|
||||||
|
id: Date.now(),
|
||||||
|
payload: JSON.parse(payload) as T,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("hashchange", listener)
|
||||||
|
return () => window.removeEventListener("hashchange", listener)
|
||||||
|
},
|
||||||
|
|
||||||
|
async emit(event: string, payload?: unknown) {
|
||||||
|
window.location.hash = `${event}:${JSON.stringify(payload)}`
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { v1 } from './v/1'
|
import { v1 } from "./v/1"
|
||||||
|
|
||||||
export type {
|
export type {
|
||||||
IoV1,
|
IoV1,
|
||||||
|
|
@ -8,8 +8,8 @@ export type {
|
||||||
OpenExternalLinkResponse,
|
OpenExternalLinkResponse,
|
||||||
Event,
|
Event,
|
||||||
EventCallback,
|
EventCallback,
|
||||||
UnlistenFn
|
UnlistenFn,
|
||||||
} from './v/1'
|
} from "./v/1"
|
||||||
|
|
||||||
export const VERSIONS = {
|
export const VERSIONS = {
|
||||||
v1,
|
v1,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import type { VersionedAPI } from '@type/versioning'
|
import type { VersionedAPI } from "@type/versioning"
|
||||||
|
|
||||||
export interface Event<T> {
|
export interface Event<T> {
|
||||||
event: string
|
event: string
|
||||||
|
|
@ -42,20 +42,11 @@ export interface IoV1 {
|
||||||
opts: OpenExternalLinkOptions
|
opts: OpenExternalLinkOptions
|
||||||
) => Promise<OpenExternalLinkResponse>
|
) => Promise<OpenExternalLinkResponse>
|
||||||
|
|
||||||
listen: <T>(
|
listen: <T>(event: string, handler: EventCallback<T>) => Promise<UnlistenFn>
|
||||||
event: string,
|
|
||||||
handler: EventCallback<T>
|
|
||||||
) => Promise<UnlistenFn>
|
|
||||||
|
|
||||||
once: <T>(
|
once: <T>(event: string, handler: EventCallback<T>) => Promise<UnlistenFn>
|
||||||
event: string,
|
|
||||||
handler: EventCallback<T>
|
|
||||||
) => Promise<UnlistenFn>
|
|
||||||
|
|
||||||
emit: (
|
emit: (event: string, payload?: unknown) => Promise<void>
|
||||||
event: string,
|
|
||||||
payload?: unknown
|
|
||||||
) => Promise<void>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const v1: VersionedAPI<IoV1> = {
|
export const v1: VersionedAPI<IoV1> = {
|
||||||
|
|
@ -66,5 +57,5 @@ export const v1: VersionedAPI<IoV1> = {
|
||||||
listen: async () => () => {},
|
listen: async () => () => {},
|
||||||
once: async () => () => {},
|
once: async () => () => {},
|
||||||
emit: async () => {},
|
emit: async () => {},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { implementation as relayV1 } from './v/1'
|
import { implementation as relayV1 } from "./v/1"
|
||||||
|
|
||||||
export const RELAY_IMPLS = {
|
export const RELAY_IMPLS = {
|
||||||
v1: relayV1,
|
v1: relayV1,
|
||||||
|
|
|
||||||
|
|
@ -1,200 +1,202 @@
|
||||||
import type { VersionedAPI } from '@type/versioning'
|
import type { VersionedAPI } from "@type/versioning"
|
||||||
import {
|
import {
|
||||||
type RelayV1,
|
type RelayV1,
|
||||||
type RelayRequest,
|
type RelayRequest,
|
||||||
type RelayRequestEvents,
|
type RelayRequestEvents,
|
||||||
type RelayEventEmitter,
|
type RelayEventEmitter,
|
||||||
type RelayResponse,
|
type RelayResponse,
|
||||||
type RelayError,
|
type RelayError,
|
||||||
body,
|
body,
|
||||||
relayRequestToNativeAdapter,
|
relayRequestToNativeAdapter,
|
||||||
} from '@relay/v/1'
|
} from "@relay/v/1"
|
||||||
import * as E from 'fp-ts/Either'
|
import * as E from "fp-ts/Either"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
execute,
|
execute,
|
||||||
cancel,
|
cancel,
|
||||||
type Request,
|
type Request as _Request,
|
||||||
type RequestResult
|
type RequestResult,
|
||||||
} from '@hoppscotch/plugin-relay'
|
} from "@hoppscotch/plugin-relay"
|
||||||
|
|
||||||
export const implementation: VersionedAPI<RelayV1> = {
|
export const implementation: VersionedAPI<RelayV1> = {
|
||||||
version: { major: 1, minor: 0, patch: 0 },
|
version: { major: 1, minor: 0, patch: 0 },
|
||||||
api: {
|
api: {
|
||||||
id: 'desktop',
|
id: "desktop",
|
||||||
capabilities: {
|
capabilities: {
|
||||||
method: new Set([
|
method: new Set([
|
||||||
'GET',
|
"GET",
|
||||||
'POST',
|
"POST",
|
||||||
'PUT',
|
"PUT",
|
||||||
'DELETE',
|
"DELETE",
|
||||||
'PATCH',
|
"PATCH",
|
||||||
'HEAD',
|
"HEAD",
|
||||||
'OPTIONS'
|
"OPTIONS",
|
||||||
]),
|
]),
|
||||||
header: new Set([
|
header: new Set(["stringvalue", "arrayvalue", "multivalue"]),
|
||||||
'stringvalue',
|
content: new Set([
|
||||||
'arrayvalue',
|
"text",
|
||||||
'multivalue'
|
"json",
|
||||||
]),
|
"xml",
|
||||||
content: new Set([
|
"form",
|
||||||
'text',
|
"binary",
|
||||||
'json',
|
"multipart",
|
||||||
'xml',
|
"urlencoded",
|
||||||
'form',
|
"stream",
|
||||||
'binary',
|
"compression",
|
||||||
'multipart',
|
]),
|
||||||
'urlencoded',
|
auth: new Set(["basic", "bearer", "digest", "oauth2", "apikey"]),
|
||||||
'stream',
|
security: new Set([
|
||||||
'compression'
|
"clientcertificates",
|
||||||
]),
|
"cacertificates",
|
||||||
auth: new Set([
|
"certificatevalidation",
|
||||||
'basic',
|
"hostverification",
|
||||||
'bearer',
|
"peerverification",
|
||||||
'digest',
|
]),
|
||||||
'oauth2',
|
proxy: new Set(["http", "https", "authentication", "certificates"]),
|
||||||
'apikey'
|
advanced: new Set([
|
||||||
]),
|
"retry",
|
||||||
security: new Set([
|
"redirects",
|
||||||
'clientcertificates',
|
"timeout",
|
||||||
'cacertificates',
|
"cookies",
|
||||||
'certificatevalidation',
|
"keepalive",
|
||||||
'hostverification',
|
"tcpoptions",
|
||||||
'peerverification'
|
"http2",
|
||||||
]),
|
"http3",
|
||||||
proxy: new Set([
|
]),
|
||||||
'http',
|
},
|
||||||
'https',
|
|
||||||
'authentication',
|
canHandle(request: RelayRequest) {
|
||||||
'certificates'
|
if (!this.capabilities.method.has(request.method)) {
|
||||||
]),
|
return E.left({
|
||||||
advanced: new Set([
|
kind: "unsupported_feature",
|
||||||
'retry',
|
feature: "method",
|
||||||
'redirects',
|
message: `Method ${request.method} is not supported`,
|
||||||
'timeout',
|
relay: "desktop",
|
||||||
'cookies',
|
})
|
||||||
'keepalive',
|
}
|
||||||
'tcpoptions',
|
|
||||||
'http2',
|
if (
|
||||||
'http3'
|
request.content &&
|
||||||
])
|
!this.capabilities.content.has(request.content.kind)
|
||||||
|
) {
|
||||||
|
return E.left({
|
||||||
|
kind: "unsupported_feature",
|
||||||
|
feature: "content",
|
||||||
|
message: `Content type ${request.content.kind} is not supported`,
|
||||||
|
relay: "desktop",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.auth && !this.capabilities.auth.has(request.auth.kind)) {
|
||||||
|
return E.left({
|
||||||
|
kind: "unsupported_feature",
|
||||||
|
feature: "authentication",
|
||||||
|
message: `Authentication type ${request.auth.kind} is not supported`,
|
||||||
|
relay: "desktop",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
request.security?.certificates &&
|
||||||
|
!this.capabilities.security.has("clientcertificates")
|
||||||
|
) {
|
||||||
|
return E.left({
|
||||||
|
kind: "unsupported_feature",
|
||||||
|
feature: "security",
|
||||||
|
message: "Client certificates are not supported",
|
||||||
|
relay: "desktop",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
request.proxy &&
|
||||||
|
!this.capabilities.proxy.has(
|
||||||
|
request.proxy.url.startsWith("https") ? "https" : "http"
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return E.left({
|
||||||
|
kind: "unsupported_feature",
|
||||||
|
feature: "proxy",
|
||||||
|
message: `Proxy protocol ${request.proxy.url.split(":")[0]} is not supported`,
|
||||||
|
relay: "desktop",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return E.right(true)
|
||||||
|
},
|
||||||
|
|
||||||
|
execute(request: RelayRequest) {
|
||||||
|
const emitter: RelayEventEmitter<RelayRequestEvents> = {
|
||||||
|
on: () => () => {},
|
||||||
|
once: () => () => {},
|
||||||
|
off: () => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
const responsePromise = relayRequestToNativeAdapter(request)
|
||||||
|
.then((request) => {
|
||||||
|
// SAFETY: Type assertion is safe because:
|
||||||
|
// 1. The capabilities system prevents requests with unsupported methods from reaching this point
|
||||||
|
// 2. Content types not supported by the plugin are filtered by capabilities
|
||||||
|
// 3. Authentication methods are validated through capabilities
|
||||||
|
// 4. The plugin's Request type is a subset of our Request type
|
||||||
|
const pluginRequest = {
|
||||||
|
id: request.id,
|
||||||
|
url: request.url,
|
||||||
|
method: request.method,
|
||||||
|
version: request.version,
|
||||||
|
headers: request.headers,
|
||||||
|
params: request.params,
|
||||||
|
content: request.content,
|
||||||
|
auth: request.auth,
|
||||||
|
security: request.security,
|
||||||
|
proxy: request.proxy,
|
||||||
|
meta: request.meta,
|
||||||
|
}
|
||||||
|
|
||||||
|
return execute(pluginRequest)
|
||||||
|
})
|
||||||
|
.then((result: RequestResult): E.Either<RelayError, RelayResponse> => {
|
||||||
|
if (result.kind === "success") {
|
||||||
|
const response: RelayResponse = {
|
||||||
|
id: result.response.id,
|
||||||
|
status: result.response.status,
|
||||||
|
statusText: result.response.statusText,
|
||||||
|
version: result.response.version,
|
||||||
|
headers: result.response.headers,
|
||||||
|
cookies: result.response.cookies,
|
||||||
|
body: body.body(
|
||||||
|
result.response.body.body,
|
||||||
|
result.response.body.mediaType
|
||||||
|
),
|
||||||
|
meta: {
|
||||||
|
timing: {
|
||||||
|
start: result.response.meta.timing.start,
|
||||||
|
end: result.response.meta.timing.end,
|
||||||
|
},
|
||||||
|
size: result.response.meta.size,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return E.right(response)
|
||||||
|
}
|
||||||
|
return E.left(result.error)
|
||||||
|
})
|
||||||
|
.catch((error: unknown): E.Either<RelayError, RelayResponse> => {
|
||||||
|
const networkError: RelayError = {
|
||||||
|
kind: "network",
|
||||||
|
message:
|
||||||
|
error instanceof Error ? error.message : "Unknown error occurred",
|
||||||
|
cause: error,
|
||||||
|
}
|
||||||
|
return E.left(networkError)
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
cancel: async () => {
|
||||||
|
await cancel(request.id)
|
||||||
},
|
},
|
||||||
|
emitter,
|
||||||
canHandle(request: RelayRequest) {
|
response: responsePromise,
|
||||||
if (!this.capabilities.method.has(request.method)) {
|
}
|
||||||
return E.left({
|
},
|
||||||
kind: "unsupported_feature",
|
},
|
||||||
feature: "method",
|
|
||||||
message: `Method ${request.method} is not supported`,
|
|
||||||
relay: "desktop"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.content && !this.capabilities.content.has(request.content.kind)) {
|
|
||||||
return E.left({
|
|
||||||
kind: "unsupported_feature",
|
|
||||||
feature: "content",
|
|
||||||
message: `Content type ${request.content.kind} is not supported`,
|
|
||||||
relay: "desktop"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.auth && !this.capabilities.auth.has(request.auth.kind)) {
|
|
||||||
return E.left({
|
|
||||||
kind: "unsupported_feature",
|
|
||||||
feature: "authentication",
|
|
||||||
message: `Authentication type ${request.auth.kind} is not supported`,
|
|
||||||
relay: "desktop"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.security?.certificates && !this.capabilities.security.has('clientcertificates')) {
|
|
||||||
return E.left({
|
|
||||||
kind: "unsupported_feature",
|
|
||||||
feature: "security",
|
|
||||||
message: "Client certificates are not supported",
|
|
||||||
relay: "desktop"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.proxy && !this.capabilities.proxy.has(request.proxy.url.startsWith('https') ? 'https' : 'http')) {
|
|
||||||
return E.left({
|
|
||||||
kind: "unsupported_feature",
|
|
||||||
feature: "proxy",
|
|
||||||
message: `Proxy protocol ${request.proxy.url.split(':')[0]} is not supported`,
|
|
||||||
relay: "desktop"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return E.right(true)
|
|
||||||
},
|
|
||||||
|
|
||||||
execute(request: RelayRequest) {
|
|
||||||
const emitter: RelayEventEmitter<RelayRequestEvents> = {
|
|
||||||
on: () => () => {},
|
|
||||||
once: () => () => {},
|
|
||||||
off: () => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const responsePromise = relayRequestToNativeAdapter(request)
|
|
||||||
.then(request => {
|
|
||||||
// SAFETY: Type assertion is safe because:
|
|
||||||
// 1. The capabilities system prevents requests with unsupported methods from reaching this point
|
|
||||||
// 2. Content types not supported by the plugin are filtered by capabilities
|
|
||||||
// 3. Authentication methods are validated through capabilities
|
|
||||||
// 4. The plugin's Request type is a subset of our Request type
|
|
||||||
const pluginRequest = {
|
|
||||||
id: request.id,
|
|
||||||
url: request.url,
|
|
||||||
method: request.method,
|
|
||||||
version: request.version,
|
|
||||||
headers: request.headers,
|
|
||||||
params: request.params,
|
|
||||||
content: request.content,
|
|
||||||
auth: request.auth,
|
|
||||||
security: request.security,
|
|
||||||
proxy: request.proxy,
|
|
||||||
meta: request.meta,
|
|
||||||
}
|
|
||||||
|
|
||||||
return execute(pluginRequest)
|
|
||||||
})
|
|
||||||
.then((result: RequestResult): E.Either<RelayError, RelayResponse> => {
|
|
||||||
if (result.kind === 'success') {
|
|
||||||
const response: RelayResponse = {
|
|
||||||
id: result.response.id,
|
|
||||||
status: result.response.status,
|
|
||||||
statusText: result.response.statusText,
|
|
||||||
version: result.response.version,
|
|
||||||
headers: result.response.headers,
|
|
||||||
cookies: result.response.cookies,
|
|
||||||
body: body.body(result.response.body.body, result.response.body.mediaType),
|
|
||||||
meta: {
|
|
||||||
timing: {
|
|
||||||
start: result.response.meta.timing.start,
|
|
||||||
end: result.response.meta.timing.end,
|
|
||||||
},
|
|
||||||
size: result.response.meta.size,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return E.right(response)
|
|
||||||
}
|
|
||||||
return E.left(result.error)
|
|
||||||
})
|
|
||||||
.catch((error: unknown): E.Either<RelayError, RelayResponse> => {
|
|
||||||
const networkError: RelayError = {
|
|
||||||
kind: 'network',
|
|
||||||
message: error instanceof Error ? error.message : 'Unknown error occurred',
|
|
||||||
cause: error
|
|
||||||
}
|
|
||||||
return E.left(networkError)
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
cancel: async () => { await cancel(request.id) },
|
|
||||||
emitter,
|
|
||||||
response: responsePromise
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { implementation as relayV1 } from './v/1'
|
import { implementation as relayV1 } from "./v/1"
|
||||||
|
|
||||||
export const RELAY_IMPLS = {
|
export const RELAY_IMPLS = {
|
||||||
v1: relayV1,
|
v1: relayV1,
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import {
|
||||||
} from "@relay/v/1"
|
} from "@relay/v/1"
|
||||||
import type { VersionedAPI } from "@type/versioning"
|
import type { VersionedAPI } from "@type/versioning"
|
||||||
|
|
||||||
import { AwsV4Signer } from "aws4fetch"
|
import { AwsV4Signer as _AwsV4Signer } from "aws4fetch"
|
||||||
import axios, { AxiosRequestConfig } from "axios"
|
import axios, { AxiosRequestConfig } from "axios"
|
||||||
|
|
||||||
import * as E from "fp-ts/Either"
|
import * as E from "fp-ts/Either"
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
import { v1 } from './v/1'
|
import { v1 } from "./v/1"
|
||||||
|
|
||||||
export type {
|
export type { RelayV1 } from "./v/1"
|
||||||
RelayV1,
|
|
||||||
} from './v/1'
|
|
||||||
|
|
||||||
export const VERSIONS = {
|
export const VERSIONS = {
|
||||||
v1,
|
v1,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export const latest = v1
|
export const latest = v1
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,4 @@
|
||||||
import { implementation as storeV1 } from './v/1'
|
import { implementation as storeV1 } from "./v/1"
|
||||||
|
|
||||||
export const STORE_IMPLS = {
|
export const STORE_IMPLS = {
|
||||||
v1: storeV1,
|
v1: storeV1,
|
||||||
|
|
|
||||||
|
|
@ -1,334 +1,388 @@
|
||||||
import * as E from 'fp-ts/Either';
|
import * as E from "fp-ts/Either"
|
||||||
|
|
||||||
import { Store } from '@tauri-apps/plugin-store';
|
import { Store } from "@tauri-apps/plugin-store"
|
||||||
|
|
||||||
import { VersionedAPI } from '@type/versioning';
|
import { VersionedAPI } from "@type/versioning"
|
||||||
import {
|
import {
|
||||||
StoreV1,
|
StoreV1,
|
||||||
StoreError,
|
StoreError,
|
||||||
StoreEvents,
|
StoreEvents,
|
||||||
StorageOptions,
|
StorageOptions,
|
||||||
StoredData,
|
StoredData,
|
||||||
StoredDataSchema,
|
StoredDataSchema,
|
||||||
StoreEventEmitter,
|
StoreEventEmitter,
|
||||||
} from '@store/v/1';
|
} from "@store/v/1"
|
||||||
|
|
||||||
type NamespacedData = Record<string, Record<string, StoredData>>;
|
type NamespacedData = Record<string, Record<string, StoredData>>
|
||||||
|
|
||||||
class TauriStoreManager {
|
class TauriStoreManager {
|
||||||
private static instances: Map<string, TauriStoreManager> = new Map();
|
private static instances: Map<string, TauriStoreManager> = new Map()
|
||||||
private store: Store | null = null;
|
private store: Store | null = null
|
||||||
private listeners = new Map<string, Set<(payload: StoreEvents['change']) => void>>();
|
private listeners = new Map<
|
||||||
private data: NamespacedData = {};
|
string,
|
||||||
private storePath: string;
|
Set<(payload: StoreEvents["change"]) => void>
|
||||||
|
>()
|
||||||
|
private data: NamespacedData = {}
|
||||||
|
private storePath: string
|
||||||
|
|
||||||
private constructor(storePath: string) {
|
private constructor(storePath: string) {
|
||||||
this.storePath = storePath;
|
this.storePath = storePath
|
||||||
|
}
|
||||||
|
|
||||||
|
static new(storePath: string): TauriStoreManager {
|
||||||
|
if (TauriStoreManager.instances.has(storePath)) {
|
||||||
|
return TauriStoreManager.instances.get(storePath)!
|
||||||
}
|
}
|
||||||
|
|
||||||
static new(storePath: string): TauriStoreManager {
|
const instance = new TauriStoreManager(storePath)
|
||||||
if (TauriStoreManager.instances.has(storePath)) {
|
TauriStoreManager.instances.set(storePath, instance)
|
||||||
return TauriStoreManager.instances.get(storePath)!;
|
return instance
|
||||||
|
}
|
||||||
|
|
||||||
|
static async closeAll(): Promise<void> {
|
||||||
|
const closePromises = Array.from(TauriStoreManager.instances.values()).map(
|
||||||
|
(instance) => instance.close()
|
||||||
|
)
|
||||||
|
await Promise.all(closePromises)
|
||||||
|
TauriStoreManager.instances.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
static async closeStore(storePath: string): Promise<void> {
|
||||||
|
const instance = TauriStoreManager.instances.get(storePath)
|
||||||
|
if (instance) {
|
||||||
|
await instance.close()
|
||||||
|
TauriStoreManager.instances.delete(storePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async init(): Promise<void> {
|
||||||
|
if (!this.store) {
|
||||||
|
this.store = await Store.load(this.storePath)
|
||||||
|
const loadedData = await this.store.get<NamespacedData>("data")
|
||||||
|
this.data = loadedData ?? {}
|
||||||
|
|
||||||
|
this.store.onChange((_, value: NamespacedData | undefined) => {
|
||||||
|
if (value) {
|
||||||
|
this.data = value
|
||||||
|
this.notifyListeners()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private notifyListeners(): void {
|
||||||
|
for (const [key, listeners] of this.listeners.entries()) {
|
||||||
|
const [namespace, dataKey] = key.split(":")
|
||||||
|
const value = this.data[namespace]?.[dataKey]
|
||||||
|
listeners.forEach((listener) =>
|
||||||
|
listener({
|
||||||
|
namespace,
|
||||||
|
key: dataKey,
|
||||||
|
value: value?.data,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async set(namespace: string, key: string, value: StoredData): Promise<void> {
|
||||||
|
if (!this.store) throw new Error("Store not initialized")
|
||||||
|
|
||||||
|
const validated = StoredDataSchema.parse(value)
|
||||||
|
this.data[namespace] = this.data[namespace] || {}
|
||||||
|
this.data[namespace][key] = validated
|
||||||
|
await this.store.set("data", this.data)
|
||||||
|
await this.store.save()
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRaw(
|
||||||
|
namespace: string,
|
||||||
|
key: string
|
||||||
|
): Promise<StoredData | undefined> {
|
||||||
|
const rawValue = this.data[namespace]?.[key]
|
||||||
|
if (!rawValue) return undefined
|
||||||
|
|
||||||
|
const validated = StoredDataSchema.parse(rawValue)
|
||||||
|
return validated
|
||||||
|
}
|
||||||
|
|
||||||
|
async get<T>(namespace: string, key: string): Promise<T | undefined> {
|
||||||
|
const storedData = await this.getRaw(namespace, key)
|
||||||
|
return storedData?.data as T | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
async has(namespace: string, key: string): Promise<boolean> {
|
||||||
|
return !!this.data[namespace]?.[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(namespace: string, key: string): Promise<boolean> {
|
||||||
|
if (!this.store) throw new Error("Store not initialized")
|
||||||
|
|
||||||
|
if (this.data[namespace]?.[key]) {
|
||||||
|
delete this.data[namespace][key]
|
||||||
|
if (Object.keys(this.data[namespace]).length === 0) {
|
||||||
|
delete this.data[namespace]
|
||||||
|
}
|
||||||
|
await this.store.set("data", this.data)
|
||||||
|
await this.store.save()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
async clear(namespace?: string): Promise<void> {
|
||||||
|
if (!this.store) throw new Error("Store not initialized")
|
||||||
|
|
||||||
|
if (namespace) {
|
||||||
|
delete this.data[namespace]
|
||||||
|
} else {
|
||||||
|
this.data = {}
|
||||||
|
}
|
||||||
|
await this.store.set("data", this.data)
|
||||||
|
await this.store.save()
|
||||||
|
}
|
||||||
|
|
||||||
|
async listNamespaces(): Promise<string[]> {
|
||||||
|
return Object.keys(this.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
async listKeys(namespace: string): Promise<string[]> {
|
||||||
|
return Object.keys(this.data[namespace] || {})
|
||||||
|
}
|
||||||
|
|
||||||
|
async watch(
|
||||||
|
namespace: string,
|
||||||
|
key: string
|
||||||
|
): Promise<StoreEventEmitter<StoreEvents>> {
|
||||||
|
const watchKey = `${namespace}:${key}`
|
||||||
|
return {
|
||||||
|
on: <K extends keyof StoreEvents>(
|
||||||
|
event: K,
|
||||||
|
handler: (payload: StoreEvents[K]) => void
|
||||||
|
) => {
|
||||||
|
if (event !== "change") return () => {}
|
||||||
|
|
||||||
|
if (!this.listeners.has(watchKey)) {
|
||||||
|
this.listeners.set(watchKey, new Set())
|
||||||
|
}
|
||||||
|
this.listeners
|
||||||
|
.get(watchKey)!
|
||||||
|
.add(handler as (payload: StoreEvents["change"]) => void)
|
||||||
|
return () =>
|
||||||
|
this.listeners
|
||||||
|
.get(watchKey)
|
||||||
|
?.delete(handler as (payload: StoreEvents["change"]) => void)
|
||||||
|
},
|
||||||
|
once: <K extends keyof StoreEvents>(
|
||||||
|
event: K,
|
||||||
|
handler: (payload: StoreEvents[K]) => void
|
||||||
|
) => {
|
||||||
|
if (event !== "change") return () => {}
|
||||||
|
|
||||||
|
const wrapper = (value: StoreEvents["change"]) => {
|
||||||
|
handler(value as StoreEvents[K])
|
||||||
|
this.listeners.get(watchKey)?.delete(wrapper)
|
||||||
}
|
}
|
||||||
|
|
||||||
const instance = new TauriStoreManager(storePath);
|
if (!this.listeners.has(watchKey)) {
|
||||||
TauriStoreManager.instances.set(storePath, instance);
|
this.listeners.set(watchKey, new Set())
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
static async closeAll(): Promise<void> {
|
|
||||||
const closePromises = Array.from(TauriStoreManager.instances.values())
|
|
||||||
.map(instance => instance.close());
|
|
||||||
await Promise.all(closePromises);
|
|
||||||
TauriStoreManager.instances.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
static async closeStore(storePath: string): Promise<void> {
|
|
||||||
const instance = TauriStoreManager.instances.get(storePath);
|
|
||||||
if (instance) {
|
|
||||||
await instance.close();
|
|
||||||
TauriStoreManager.instances.delete(storePath);
|
|
||||||
}
|
}
|
||||||
}
|
this.listeners.get(watchKey)!.add(wrapper)
|
||||||
|
return () => this.listeners.get(watchKey)?.delete(wrapper)
|
||||||
async init(): Promise<void> {
|
},
|
||||||
if (!this.store) {
|
off: <K extends keyof StoreEvents>(
|
||||||
this.store = await Store.load(this.storePath);
|
event: K,
|
||||||
const loadedData = await this.store.get<NamespacedData>('data');
|
handler: (payload: StoreEvents[K]) => void
|
||||||
this.data = loadedData ?? {};
|
) => {
|
||||||
|
if (event === "change") {
|
||||||
this.store.onChange((_, value: NamespacedData | undefined) => {
|
this.listeners
|
||||||
if (value) {
|
.get(watchKey)
|
||||||
this.data = value;
|
?.delete(handler as (payload: StoreEvents["change"]) => void)
|
||||||
this.notifyListeners();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private notifyListeners(): void {
|
async close(): Promise<void> {
|
||||||
for (const [key, listeners] of this.listeners.entries()) {
|
if (this.store) {
|
||||||
const [namespace, dataKey] = key.split(':');
|
await this.store.close()
|
||||||
const value = this.data[namespace]?.[dataKey];
|
this.store = null
|
||||||
listeners.forEach(listener =>
|
this.data = {}
|
||||||
listener({
|
this.listeners.clear()
|
||||||
namespace,
|
TauriStoreManager.instances.delete(this.storePath)
|
||||||
key: dataKey,
|
|
||||||
value: value?.data,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async set(namespace: string, key: string, value: StoredData): Promise<void> {
|
|
||||||
if (!this.store) throw new Error('Store not initialized');
|
|
||||||
|
|
||||||
const validated = StoredDataSchema.parse(value);
|
|
||||||
this.data[namespace] = this.data[namespace] || {};
|
|
||||||
this.data[namespace][key] = validated;
|
|
||||||
await this.store.set('data', this.data);
|
|
||||||
await this.store.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
async getRaw(namespace: string, key: string): Promise<StoredData | undefined> {
|
|
||||||
const rawValue = this.data[namespace]?.[key];
|
|
||||||
if (!rawValue) return undefined;
|
|
||||||
|
|
||||||
const validated = StoredDataSchema.parse(rawValue);
|
|
||||||
return validated;
|
|
||||||
}
|
|
||||||
|
|
||||||
async get<T>(namespace: string, key: string): Promise<T | undefined> {
|
|
||||||
const storedData = await this.getRaw(namespace, key);
|
|
||||||
return storedData?.data as T | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
async has(namespace: string, key: string): Promise<boolean> {
|
|
||||||
return !!this.data[namespace]?.[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
async delete(namespace: string, key: string): Promise<boolean> {
|
|
||||||
if (!this.store) throw new Error('Store not initialized');
|
|
||||||
|
|
||||||
if (this.data[namespace]?.[key]) {
|
|
||||||
delete this.data[namespace][key];
|
|
||||||
if (Object.keys(this.data[namespace]).length === 0) {
|
|
||||||
delete this.data[namespace];
|
|
||||||
}
|
|
||||||
await this.store.set('data', this.data);
|
|
||||||
await this.store.save();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async clear(namespace?: string): Promise<void> {
|
|
||||||
if (!this.store) throw new Error('Store not initialized');
|
|
||||||
|
|
||||||
if (namespace) {
|
|
||||||
delete this.data[namespace];
|
|
||||||
} else {
|
|
||||||
this.data = {};
|
|
||||||
}
|
|
||||||
await this.store.set('data', this.data);
|
|
||||||
await this.store.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
async listNamespaces(): Promise<string[]> {
|
|
||||||
return Object.keys(this.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
async listKeys(namespace: string): Promise<string[]> {
|
|
||||||
return Object.keys(this.data[namespace] || {});
|
|
||||||
}
|
|
||||||
|
|
||||||
async watch(namespace: string, key: string): Promise<StoreEventEmitter<StoreEvents>> {
|
|
||||||
const watchKey = `${namespace}:${key}`;
|
|
||||||
return {
|
|
||||||
on: <K extends keyof StoreEvents>(
|
|
||||||
event: K,
|
|
||||||
handler: (payload: StoreEvents[K]) => void
|
|
||||||
) => {
|
|
||||||
if (event !== 'change') return () => {};
|
|
||||||
|
|
||||||
if (!this.listeners.has(watchKey)) {
|
|
||||||
this.listeners.set(watchKey, new Set());
|
|
||||||
}
|
|
||||||
this.listeners.get(watchKey)!.add(handler as (payload: StoreEvents['change']) => void);
|
|
||||||
return () => this.listeners.get(watchKey)?.delete(handler as (payload: StoreEvents['change']) => void);
|
|
||||||
},
|
|
||||||
once: <K extends keyof StoreEvents>(
|
|
||||||
event: K,
|
|
||||||
handler: (payload: StoreEvents[K]) => void
|
|
||||||
) => {
|
|
||||||
if (event !== 'change') return () => {};
|
|
||||||
|
|
||||||
const wrapper = (value: StoreEvents['change']) => {
|
|
||||||
handler(value as StoreEvents[K]);
|
|
||||||
this.listeners.get(watchKey)?.delete(wrapper);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!this.listeners.has(watchKey)) {
|
|
||||||
this.listeners.set(watchKey, new Set());
|
|
||||||
}
|
|
||||||
this.listeners.get(watchKey)!.add(wrapper);
|
|
||||||
return () => this.listeners.get(watchKey)?.delete(wrapper);
|
|
||||||
},
|
|
||||||
off: <K extends keyof StoreEvents>(
|
|
||||||
event: K,
|
|
||||||
handler: (payload: StoreEvents[K]) => void
|
|
||||||
) => {
|
|
||||||
if (event === 'change') {
|
|
||||||
this.listeners.get(watchKey)?.delete(handler as (payload: StoreEvents['change']) => void);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async close(): Promise<void> {
|
|
||||||
if (this.store) {
|
|
||||||
await this.store.close();
|
|
||||||
this.store = null;
|
|
||||||
this.data = {};
|
|
||||||
this.listeners.clear();
|
|
||||||
TauriStoreManager.instances.delete(this.storePath);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const implementation: VersionedAPI<StoreV1> = {
|
export const implementation: VersionedAPI<StoreV1> = {
|
||||||
version: { major: 1, minor: 0, patch: 0 },
|
version: { major: 1, minor: 0, patch: 0 },
|
||||||
api: {
|
api: {
|
||||||
id: 'tauri-store',
|
id: "tauri-store",
|
||||||
capabilities: new Set(['permanent', 'structured', 'watch', 'namespace', 'secure']),
|
capabilities: new Set([
|
||||||
|
"permanent",
|
||||||
|
"structured",
|
||||||
|
"watch",
|
||||||
|
"namespace",
|
||||||
|
"secure",
|
||||||
|
]),
|
||||||
|
|
||||||
async init(storePath: string) {
|
async init(storePath: string) {
|
||||||
try {
|
try {
|
||||||
const manager = TauriStoreManager.new(storePath);
|
const manager = TauriStoreManager.new(storePath)
|
||||||
await manager.init();
|
await manager.init()
|
||||||
return E.right(undefined);
|
return E.right(undefined)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return E.left({
|
return E.left({
|
||||||
kind: 'storage',
|
kind: "storage",
|
||||||
message: error instanceof Error ? error.message : 'Unknown error',
|
message: error instanceof Error ? error.message : "Unknown error",
|
||||||
cause: error,
|
cause: error,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
async set(storePath: string, namespace: string, key: string, value: unknown, options?: StorageOptions): Promise<E.Either<StoreError, void>> {
|
|
||||||
try {
|
|
||||||
const manager = TauriStoreManager.new(storePath);
|
|
||||||
const existingData = await manager.getRaw(namespace, key);
|
|
||||||
const createdAt = existingData?.metadata.createdAt || new Date().toISOString()
|
|
||||||
const updatedAt = new Date().toISOString()
|
|
||||||
|
|
||||||
const storedData: StoredData = {
|
|
||||||
schemaVersion: 1,
|
|
||||||
metadata: {
|
|
||||||
createdAt,
|
|
||||||
updatedAt,
|
|
||||||
namespace,
|
|
||||||
encrypted: options?.encrypt,
|
|
||||||
compressed: options?.compress,
|
|
||||||
ttl: options?.ttl,
|
|
||||||
},
|
|
||||||
data: value,
|
|
||||||
};
|
|
||||||
|
|
||||||
await manager.set(namespace, key, storedData);
|
|
||||||
return E.right(undefined);
|
|
||||||
} catch (error) {
|
|
||||||
return E.left({
|
|
||||||
kind: 'storage',
|
|
||||||
message: error instanceof Error ? error.message : 'Unknown error',
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async get<T>(storePath: string, namespace: string, key: string): Promise<E.Either<StoreError, T | undefined>> {
|
|
||||||
try {
|
|
||||||
const manager = TauriStoreManager.new(storePath);
|
|
||||||
return E.right(await manager.get<T>(namespace, key));
|
|
||||||
} catch (error) {
|
|
||||||
return E.left({
|
|
||||||
kind: 'storage',
|
|
||||||
message: error instanceof Error ? error.message : 'Unknown error',
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async has(storePath: string, namespace: string, key: string): Promise<E.Either<StoreError, boolean>> {
|
|
||||||
try {
|
|
||||||
const manager = TauriStoreManager.new(storePath);
|
|
||||||
return E.right(await manager.has(namespace, key));
|
|
||||||
} catch (error) {
|
|
||||||
return E.left({
|
|
||||||
kind: 'storage',
|
|
||||||
message: error instanceof Error ? error.message : 'Unknown error',
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async remove(storePath: string, namespace: string, key: string): Promise<E.Either<StoreError, boolean>> {
|
|
||||||
try {
|
|
||||||
const manager = TauriStoreManager.new(storePath);
|
|
||||||
return E.right(await manager.delete(namespace, key));
|
|
||||||
} catch (error) {
|
|
||||||
return E.left({
|
|
||||||
kind: 'storage',
|
|
||||||
message: error instanceof Error ? error.message : 'Unknown error',
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async clear(storePath: string, namespace?: string): Promise<E.Either<StoreError, void>> {
|
|
||||||
try {
|
|
||||||
const manager = TauriStoreManager.new(storePath);
|
|
||||||
await manager.clear(namespace);
|
|
||||||
return E.right(undefined);
|
|
||||||
} catch (error) {
|
|
||||||
return E.left({
|
|
||||||
kind: 'storage',
|
|
||||||
message: error instanceof Error ? error.message : 'Unknown error',
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async listNamespaces(storePath: string): Promise<E.Either<StoreError, string[]>> {
|
|
||||||
try {
|
|
||||||
const manager = TauriStoreManager.new(storePath);
|
|
||||||
return E.right(await manager.listNamespaces());
|
|
||||||
} catch (error) {
|
|
||||||
return E.left({
|
|
||||||
kind: 'storage',
|
|
||||||
message: error instanceof Error ? error.message : 'Unknown error',
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async listKeys(storePath: string, namespace: string): Promise<E.Either<StoreError, string[]>> {
|
|
||||||
try {
|
|
||||||
const manager = TauriStoreManager.new(storePath);
|
|
||||||
return E.right(await manager.listKeys(namespace));
|
|
||||||
} catch (error) {
|
|
||||||
return E.left({
|
|
||||||
kind: 'storage',
|
|
||||||
message: error instanceof Error ? error.message : 'Unknown error',
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async watch(storePath: string, namespace: string, key: string): Promise<StoreEventEmitter<StoreEvents>> {
|
|
||||||
const manager = TauriStoreManager.new(storePath);
|
|
||||||
return manager.watch(namespace, key);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
|
||||||
|
async set(
|
||||||
|
storePath: string,
|
||||||
|
namespace: string,
|
||||||
|
key: string,
|
||||||
|
value: unknown,
|
||||||
|
options?: StorageOptions
|
||||||
|
): Promise<E.Either<StoreError, void>> {
|
||||||
|
try {
|
||||||
|
const manager = TauriStoreManager.new(storePath)
|
||||||
|
const existingData = await manager.getRaw(namespace, key)
|
||||||
|
const createdAt =
|
||||||
|
existingData?.metadata.createdAt || new Date().toISOString()
|
||||||
|
const updatedAt = new Date().toISOString()
|
||||||
|
|
||||||
|
const storedData: StoredData = {
|
||||||
|
schemaVersion: 1,
|
||||||
|
metadata: {
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
|
namespace,
|
||||||
|
encrypted: options?.encrypt,
|
||||||
|
compressed: options?.compress,
|
||||||
|
ttl: options?.ttl,
|
||||||
|
},
|
||||||
|
data: value,
|
||||||
|
}
|
||||||
|
|
||||||
|
await manager.set(namespace, key, storedData)
|
||||||
|
return E.right(undefined)
|
||||||
|
} catch (error) {
|
||||||
|
return E.left({
|
||||||
|
kind: "storage",
|
||||||
|
message: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
cause: error,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async get<T>(
|
||||||
|
storePath: string,
|
||||||
|
namespace: string,
|
||||||
|
key: string
|
||||||
|
): Promise<E.Either<StoreError, T | undefined>> {
|
||||||
|
try {
|
||||||
|
const manager = TauriStoreManager.new(storePath)
|
||||||
|
return E.right(await manager.get<T>(namespace, key))
|
||||||
|
} catch (error) {
|
||||||
|
return E.left({
|
||||||
|
kind: "storage",
|
||||||
|
message: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
cause: error,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async has(
|
||||||
|
storePath: string,
|
||||||
|
namespace: string,
|
||||||
|
key: string
|
||||||
|
): Promise<E.Either<StoreError, boolean>> {
|
||||||
|
try {
|
||||||
|
const manager = TauriStoreManager.new(storePath)
|
||||||
|
return E.right(await manager.has(namespace, key))
|
||||||
|
} catch (error) {
|
||||||
|
return E.left({
|
||||||
|
kind: "storage",
|
||||||
|
message: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
cause: error,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async remove(
|
||||||
|
storePath: string,
|
||||||
|
namespace: string,
|
||||||
|
key: string
|
||||||
|
): Promise<E.Either<StoreError, boolean>> {
|
||||||
|
try {
|
||||||
|
const manager = TauriStoreManager.new(storePath)
|
||||||
|
return E.right(await manager.delete(namespace, key))
|
||||||
|
} catch (error) {
|
||||||
|
return E.left({
|
||||||
|
kind: "storage",
|
||||||
|
message: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
cause: error,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async clear(
|
||||||
|
storePath: string,
|
||||||
|
namespace?: string
|
||||||
|
): Promise<E.Either<StoreError, void>> {
|
||||||
|
try {
|
||||||
|
const manager = TauriStoreManager.new(storePath)
|
||||||
|
await manager.clear(namespace)
|
||||||
|
return E.right(undefined)
|
||||||
|
} catch (error) {
|
||||||
|
return E.left({
|
||||||
|
kind: "storage",
|
||||||
|
message: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
cause: error,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async listNamespaces(
|
||||||
|
storePath: string
|
||||||
|
): Promise<E.Either<StoreError, string[]>> {
|
||||||
|
try {
|
||||||
|
const manager = TauriStoreManager.new(storePath)
|
||||||
|
return E.right(await manager.listNamespaces())
|
||||||
|
} catch (error) {
|
||||||
|
return E.left({
|
||||||
|
kind: "storage",
|
||||||
|
message: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
cause: error,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async listKeys(
|
||||||
|
storePath: string,
|
||||||
|
namespace: string
|
||||||
|
): Promise<E.Either<StoreError, string[]>> {
|
||||||
|
try {
|
||||||
|
const manager = TauriStoreManager.new(storePath)
|
||||||
|
return E.right(await manager.listKeys(namespace))
|
||||||
|
} catch (error) {
|
||||||
|
return E.left({
|
||||||
|
kind: "storage",
|
||||||
|
message: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
cause: error,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async watch(
|
||||||
|
storePath: string,
|
||||||
|
namespace: string,
|
||||||
|
key: string
|
||||||
|
): Promise<StoreEventEmitter<StoreEvents>> {
|
||||||
|
const manager = TauriStoreManager.new(storePath)
|
||||||
|
return manager.watch(namespace, key)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { implementation as storeV1 } from './v/1'
|
import { implementation as storeV1 } from "./v/1"
|
||||||
|
|
||||||
export const STORE_IMPLS = {
|
export const STORE_IMPLS = {
|
||||||
v1: storeV1,
|
v1: storeV1,
|
||||||
|
|
|
||||||
|
|
@ -1,262 +1,284 @@
|
||||||
import * as E from 'fp-ts/Either';
|
import * as E from "fp-ts/Either"
|
||||||
import superjson from 'superjson';
|
import superjson from "superjson"
|
||||||
|
|
||||||
import type { VersionedAPI } from '@type/versioning';
|
import type { VersionedAPI } from "@type/versioning"
|
||||||
import {
|
import {
|
||||||
StoreV1,
|
StoreV1,
|
||||||
StoredData,
|
StoredData,
|
||||||
StoredDataSchema,
|
StoredDataSchema,
|
||||||
StoreEvents,
|
StoreEvents,
|
||||||
StoreEventEmitter,
|
StoreEventEmitter,
|
||||||
} from '@store/v/1';
|
} from "@store/v/1"
|
||||||
|
|
||||||
class BrowserStoreManager {
|
class BrowserStoreManager {
|
||||||
private static instance: BrowserStoreManager;
|
private static instance: BrowserStoreManager
|
||||||
private listeners = new Map<string, Set<(payload: StoreEvents['change']) => void>>();
|
private listeners = new Map<
|
||||||
|
string,
|
||||||
|
Set<(payload: StoreEvents["change"]) => void>
|
||||||
|
>()
|
||||||
|
|
||||||
private constructor() {}
|
private constructor() {}
|
||||||
|
|
||||||
static new(): BrowserStoreManager {
|
static new(): BrowserStoreManager {
|
||||||
if (!BrowserStoreManager.instance) {
|
if (!BrowserStoreManager.instance) {
|
||||||
BrowserStoreManager.instance = new BrowserStoreManager();
|
BrowserStoreManager.instance = new BrowserStoreManager()
|
||||||
|
}
|
||||||
|
return BrowserStoreManager.instance
|
||||||
|
}
|
||||||
|
|
||||||
|
private getFullKey(namespace: string, key: string): string {
|
||||||
|
return `${namespace}:${key}`
|
||||||
|
}
|
||||||
|
|
||||||
|
private notifyListeners(namespace: string, key: string, value?: unknown) {
|
||||||
|
const fullKey = this.getFullKey(namespace, key)
|
||||||
|
const listeners = this.listeners.get(fullKey) || new Set()
|
||||||
|
listeners.forEach((listener) => listener({ namespace, key, value }))
|
||||||
|
}
|
||||||
|
|
||||||
|
async set(namespace: string, key: string, value: StoredData): Promise<void> {
|
||||||
|
const validated = StoredDataSchema.parse(value)
|
||||||
|
localStorage.setItem(
|
||||||
|
this.getFullKey(namespace, key),
|
||||||
|
superjson.stringify(validated)
|
||||||
|
)
|
||||||
|
this.notifyListeners(namespace, key, validated.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRaw(
|
||||||
|
namespace: string,
|
||||||
|
key: string
|
||||||
|
): Promise<StoredData | undefined> {
|
||||||
|
const rawValue = localStorage.getItem(this.getFullKey(namespace, key))
|
||||||
|
if (!rawValue) return undefined
|
||||||
|
|
||||||
|
const parsed = superjson.parse(rawValue)
|
||||||
|
const validated = StoredDataSchema.parse(parsed)
|
||||||
|
return validated
|
||||||
|
}
|
||||||
|
|
||||||
|
async get<T>(namespace: string, key: string): Promise<T | undefined> {
|
||||||
|
const storedData = await this.getRaw(namespace, key)
|
||||||
|
return storedData?.data as T
|
||||||
|
}
|
||||||
|
|
||||||
|
async has(namespace: string, key: string): Promise<boolean> {
|
||||||
|
return localStorage.getItem(this.getFullKey(namespace, key)) !== null
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(namespace: string, key: string): Promise<boolean> {
|
||||||
|
const exists = await this.has(namespace, key)
|
||||||
|
if (exists) {
|
||||||
|
localStorage.removeItem(this.getFullKey(namespace, key))
|
||||||
|
this.notifyListeners(namespace, key, undefined)
|
||||||
|
}
|
||||||
|
return exists
|
||||||
|
}
|
||||||
|
|
||||||
|
async clear(namespace?: string): Promise<void> {
|
||||||
|
if (namespace) {
|
||||||
|
const keysToRemove = Object.keys(localStorage).filter((key) =>
|
||||||
|
key.startsWith(`${namespace}:`)
|
||||||
|
)
|
||||||
|
keysToRemove.forEach((key) => localStorage.removeItem(key))
|
||||||
|
} else {
|
||||||
|
localStorage.clear()
|
||||||
|
}
|
||||||
|
this.listeners.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
async listNamespaces(): Promise<string[]> {
|
||||||
|
const namespaces = new Set<string>()
|
||||||
|
Object.keys(localStorage).forEach((key) => {
|
||||||
|
const [namespace] = key.split(":")
|
||||||
|
namespaces.add(namespace)
|
||||||
|
})
|
||||||
|
return Array.from(namespaces)
|
||||||
|
}
|
||||||
|
|
||||||
|
async listKeys(namespace: string): Promise<string[]> {
|
||||||
|
return Object.keys(localStorage)
|
||||||
|
.filter((key) => key.startsWith(`${namespace}:`))
|
||||||
|
.map((key) => key.replace(`${namespace}:`, ""))
|
||||||
|
}
|
||||||
|
|
||||||
|
async watch(
|
||||||
|
namespace: string,
|
||||||
|
key: string
|
||||||
|
): Promise<StoreEventEmitter<StoreEvents>> {
|
||||||
|
const fullKey = this.getFullKey(namespace, key)
|
||||||
|
return {
|
||||||
|
on: (event, handler) => {
|
||||||
|
if (event !== "change") return () => {}
|
||||||
|
if (!this.listeners.has(fullKey)) {
|
||||||
|
this.listeners.set(fullKey, new Set())
|
||||||
}
|
}
|
||||||
return BrowserStoreManager.instance;
|
this.listeners
|
||||||
}
|
.get(fullKey)!
|
||||||
|
.add(handler as (payload: StoreEvents["change"]) => void)
|
||||||
private getFullKey(namespace: string, key: string): string {
|
return () =>
|
||||||
return `${namespace}:${key}`;
|
this.listeners
|
||||||
}
|
.get(fullKey)
|
||||||
|
?.delete(handler as (payload: StoreEvents["change"]) => void)
|
||||||
private notifyListeners(namespace: string, key: string, value?: unknown) {
|
},
|
||||||
const fullKey = this.getFullKey(namespace, key);
|
once: (event, handler) => {
|
||||||
const listeners = this.listeners.get(fullKey) || new Set();
|
if (event !== "change") return () => {}
|
||||||
listeners.forEach(listener => listener({ namespace, key, value }));
|
const wrapper = (payload: StoreEvents["change"]) => {
|
||||||
}
|
handler(payload)
|
||||||
|
this.listeners.get(fullKey)?.delete(wrapper)
|
||||||
async set(namespace: string, key: string, value: StoredData): Promise<void> {
|
|
||||||
const validated = StoredDataSchema.parse(value);
|
|
||||||
localStorage.setItem(this.getFullKey(namespace, key), superjson.stringify(validated));
|
|
||||||
this.notifyListeners(namespace, key, validated.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getRaw(namespace: string, key: string): Promise<StoredData | undefined> {
|
|
||||||
const rawValue = localStorage.getItem(this.getFullKey(namespace, key));
|
|
||||||
if (!rawValue) return undefined;
|
|
||||||
|
|
||||||
const parsed = superjson.parse(rawValue);
|
|
||||||
const validated = StoredDataSchema.parse(parsed);
|
|
||||||
return validated;
|
|
||||||
}
|
|
||||||
|
|
||||||
async get<T>(namespace: string, key: string): Promise<T | undefined> {
|
|
||||||
const storedData = await this.getRaw(namespace, key);
|
|
||||||
return storedData?.data as T;
|
|
||||||
}
|
|
||||||
|
|
||||||
async has(namespace: string, key: string): Promise<boolean> {
|
|
||||||
return localStorage.getItem(this.getFullKey(namespace, key)) !== null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async delete(namespace: string, key: string): Promise<boolean> {
|
|
||||||
const exists = await this.has(namespace, key);
|
|
||||||
if (exists) {
|
|
||||||
localStorage.removeItem(this.getFullKey(namespace, key));
|
|
||||||
this.notifyListeners(namespace, key, undefined);
|
|
||||||
}
|
}
|
||||||
return exists;
|
if (!this.listeners.has(fullKey)) {
|
||||||
}
|
this.listeners.set(fullKey, new Set())
|
||||||
|
|
||||||
async clear(namespace?: string): Promise<void> {
|
|
||||||
if (namespace) {
|
|
||||||
const keysToRemove = Object.keys(localStorage).filter(key => key.startsWith(`${namespace}:`));
|
|
||||||
keysToRemove.forEach(key => localStorage.removeItem(key));
|
|
||||||
} else {
|
|
||||||
localStorage.clear();
|
|
||||||
}
|
}
|
||||||
this.listeners.clear();
|
this.listeners.get(fullKey)!.add(wrapper)
|
||||||
}
|
return () => this.listeners.get(fullKey)?.delete(wrapper)
|
||||||
|
},
|
||||||
async listNamespaces(): Promise<string[]> {
|
off: (event, handler) => {
|
||||||
const namespaces = new Set<string>();
|
if (event === "change") {
|
||||||
Object.keys(localStorage).forEach(key => {
|
this.listeners
|
||||||
const [namespace] = key.split(':');
|
.get(fullKey)
|
||||||
namespaces.add(namespace);
|
?.delete(handler as (payload: StoreEvents["change"]) => void)
|
||||||
});
|
}
|
||||||
return Array.from(namespaces);
|
},
|
||||||
}
|
|
||||||
|
|
||||||
async listKeys(namespace: string): Promise<string[]> {
|
|
||||||
return Object.keys(localStorage)
|
|
||||||
.filter(key => key.startsWith(`${namespace}:`))
|
|
||||||
.map(key => key.replace(`${namespace}:`, ''));
|
|
||||||
}
|
|
||||||
|
|
||||||
async watch(namespace: string, key: string): Promise<StoreEventEmitter<StoreEvents>> {
|
|
||||||
const fullKey = this.getFullKey(namespace, key);
|
|
||||||
return {
|
|
||||||
on: (event, handler) => {
|
|
||||||
if (event !== 'change') return () => {};
|
|
||||||
if (!this.listeners.has(fullKey)) {
|
|
||||||
this.listeners.set(fullKey, new Set());
|
|
||||||
}
|
|
||||||
this.listeners.get(fullKey)!.add(handler as (payload: StoreEvents['change']) => void);
|
|
||||||
return () => this.listeners.get(fullKey)?.delete(handler as (payload: StoreEvents['change']) => void);
|
|
||||||
},
|
|
||||||
once: (event, handler) => {
|
|
||||||
if (event !== 'change') return () => {};
|
|
||||||
const wrapper = (payload: StoreEvents['change']) => {
|
|
||||||
handler(payload);
|
|
||||||
this.listeners.get(fullKey)?.delete(wrapper);
|
|
||||||
};
|
|
||||||
if (!this.listeners.has(fullKey)) {
|
|
||||||
this.listeners.set(fullKey, new Set());
|
|
||||||
}
|
|
||||||
this.listeners.get(fullKey)!.add(wrapper);
|
|
||||||
return () => this.listeners.get(fullKey)?.delete(wrapper);
|
|
||||||
},
|
|
||||||
off: (event, handler) => {
|
|
||||||
if (event === 'change') {
|
|
||||||
this.listeners.get(fullKey)?.delete(handler as (payload: StoreEvents['change']) => void);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const implementation: VersionedAPI<StoreV1> = {
|
export const implementation: VersionedAPI<StoreV1> = {
|
||||||
version: { major: 1, minor: 0, patch: 0 },
|
version: { major: 1, minor: 0, patch: 0 },
|
||||||
api: {
|
api: {
|
||||||
id: 'browser-store',
|
id: "browser-store",
|
||||||
capabilities: new Set(['permanent', 'structured', 'watch', 'namespace']),
|
capabilities: new Set(["permanent", "structured", "watch", "namespace"]),
|
||||||
|
|
||||||
// `init` and other methods in `web` don't `storePath`
|
// `init` and other methods in `web` don't `storePath`
|
||||||
// but having a consistent API where first param of every method
|
// but having a consistent API where first param of every method
|
||||||
// is the path that filteres to the "realm" makes it easier to reason around
|
// is the path that filteres to the "realm" makes it easier to reason around
|
||||||
async init(_storePath) {
|
async init(_storePath) {
|
||||||
try {
|
try {
|
||||||
return E.right(undefined);
|
return E.right(undefined)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return E.left({
|
return E.left({
|
||||||
kind: 'storage',
|
kind: "storage",
|
||||||
message: e instanceof Error ? e.message : 'Unknown error',
|
message: e instanceof Error ? e.message : "Unknown error",
|
||||||
cause: e,
|
cause: e,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
async set(_storePath, namespace, key, value, options) {
|
|
||||||
try {
|
|
||||||
const manager = BrowserStoreManager.new();
|
|
||||||
const existingData = await manager.getRaw(namespace, key);
|
|
||||||
const createdAt = existingData?.metadata.createdAt || new Date().toISOString()
|
|
||||||
const updatedAt = new Date().toISOString()
|
|
||||||
|
|
||||||
const storedData: StoredData = {
|
|
||||||
schemaVersion: 1,
|
|
||||||
metadata: {
|
|
||||||
createdAt,
|
|
||||||
updatedAt,
|
|
||||||
namespace,
|
|
||||||
encrypted: options?.encrypt,
|
|
||||||
compressed: options?.compress,
|
|
||||||
ttl: options?.ttl,
|
|
||||||
},
|
|
||||||
data: value,
|
|
||||||
};
|
|
||||||
|
|
||||||
await manager.set(namespace, key, storedData);
|
|
||||||
return E.right(undefined);
|
|
||||||
} catch (e) {
|
|
||||||
return E.left({
|
|
||||||
kind: 'storage',
|
|
||||||
message: e instanceof Error ? e.message : 'Unknown error',
|
|
||||||
cause: e,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async get(_storePath, namespace, key) {
|
|
||||||
try {
|
|
||||||
const manager = BrowserStoreManager.new();
|
|
||||||
return E.right(await manager.get(namespace, key));
|
|
||||||
} catch (e) {
|
|
||||||
return E.left({
|
|
||||||
kind: 'storage',
|
|
||||||
message: e instanceof Error ? e.message : 'Unknown error',
|
|
||||||
cause: e,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async has(_storePath, namespace, key) {
|
|
||||||
try {
|
|
||||||
const manager = BrowserStoreManager.new();
|
|
||||||
return E.right(await manager.has(namespace, key));
|
|
||||||
} catch (e) {
|
|
||||||
return E.left({
|
|
||||||
kind: 'storage',
|
|
||||||
message: e instanceof Error ? e.message : 'Unknown error',
|
|
||||||
cause: e,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async remove(_storePath, namespace, key) {
|
|
||||||
try {
|
|
||||||
const manager = BrowserStoreManager.new();
|
|
||||||
return E.right(await manager.delete(namespace, key));
|
|
||||||
} catch (e) {
|
|
||||||
return E.left({
|
|
||||||
kind: 'storage',
|
|
||||||
message: e instanceof Error ? e.message : 'Unknown error',
|
|
||||||
cause: e,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async clear(_storePath, namespace) {
|
|
||||||
try {
|
|
||||||
const manager = BrowserStoreManager.new();
|
|
||||||
await manager.clear(namespace);
|
|
||||||
return E.right(undefined);
|
|
||||||
} catch (e) {
|
|
||||||
return E.left({
|
|
||||||
kind: 'storage',
|
|
||||||
message: e instanceof Error ? e.message : 'Unknown error',
|
|
||||||
cause: e,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async listNamespaces(_storePath){
|
|
||||||
try {
|
|
||||||
const manager = BrowserStoreManager.new();
|
|
||||||
return E.right(await manager.listNamespaces());
|
|
||||||
} catch (e) {
|
|
||||||
return E.left({
|
|
||||||
kind: 'storage',
|
|
||||||
message: e instanceof Error ? e.message : 'Unknown error',
|
|
||||||
cause: e,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async listKeys(_storePath, namespace) {
|
|
||||||
try {
|
|
||||||
const manager = BrowserStoreManager.new();
|
|
||||||
return E.right(await manager.listKeys(namespace));
|
|
||||||
} catch (e) {
|
|
||||||
return E.left({
|
|
||||||
kind: 'storage',
|
|
||||||
message: e instanceof Error ? e.message : 'Unknown error',
|
|
||||||
cause: e,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async watch(_storePath, namespace, key) {
|
|
||||||
const manager = BrowserStoreManager.new();
|
|
||||||
return manager.watch(namespace, key);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
|
||||||
|
async set(_storePath, namespace, key, value, options) {
|
||||||
|
try {
|
||||||
|
const manager = BrowserStoreManager.new()
|
||||||
|
const existingData = await manager.getRaw(namespace, key)
|
||||||
|
const createdAt =
|
||||||
|
existingData?.metadata.createdAt || new Date().toISOString()
|
||||||
|
const updatedAt = new Date().toISOString()
|
||||||
|
|
||||||
|
const storedData: StoredData = {
|
||||||
|
schemaVersion: 1,
|
||||||
|
metadata: {
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
|
namespace,
|
||||||
|
encrypted: options?.encrypt,
|
||||||
|
compressed: options?.compress,
|
||||||
|
ttl: options?.ttl,
|
||||||
|
},
|
||||||
|
data: value,
|
||||||
|
}
|
||||||
|
|
||||||
|
await manager.set(namespace, key, storedData)
|
||||||
|
return E.right(undefined)
|
||||||
|
} catch (e) {
|
||||||
|
return E.left({
|
||||||
|
kind: "storage",
|
||||||
|
message: e instanceof Error ? e.message : "Unknown error",
|
||||||
|
cause: e,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async get(_storePath, namespace, key) {
|
||||||
|
try {
|
||||||
|
const manager = BrowserStoreManager.new()
|
||||||
|
return E.right(await manager.get(namespace, key))
|
||||||
|
} catch (e) {
|
||||||
|
return E.left({
|
||||||
|
kind: "storage",
|
||||||
|
message: e instanceof Error ? e.message : "Unknown error",
|
||||||
|
cause: e,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async has(_storePath, namespace, key) {
|
||||||
|
try {
|
||||||
|
const manager = BrowserStoreManager.new()
|
||||||
|
return E.right(await manager.has(namespace, key))
|
||||||
|
} catch (e) {
|
||||||
|
return E.left({
|
||||||
|
kind: "storage",
|
||||||
|
message: e instanceof Error ? e.message : "Unknown error",
|
||||||
|
cause: e,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async remove(_storePath, namespace, key) {
|
||||||
|
try {
|
||||||
|
const manager = BrowserStoreManager.new()
|
||||||
|
return E.right(await manager.delete(namespace, key))
|
||||||
|
} catch (e) {
|
||||||
|
return E.left({
|
||||||
|
kind: "storage",
|
||||||
|
message: e instanceof Error ? e.message : "Unknown error",
|
||||||
|
cause: e,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async clear(_storePath, namespace) {
|
||||||
|
try {
|
||||||
|
const manager = BrowserStoreManager.new()
|
||||||
|
await manager.clear(namespace)
|
||||||
|
return E.right(undefined)
|
||||||
|
} catch (e) {
|
||||||
|
return E.left({
|
||||||
|
kind: "storage",
|
||||||
|
message: e instanceof Error ? e.message : "Unknown error",
|
||||||
|
cause: e,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async listNamespaces(_storePath) {
|
||||||
|
try {
|
||||||
|
const manager = BrowserStoreManager.new()
|
||||||
|
return E.right(await manager.listNamespaces())
|
||||||
|
} catch (e) {
|
||||||
|
return E.left({
|
||||||
|
kind: "storage",
|
||||||
|
message: e instanceof Error ? e.message : "Unknown error",
|
||||||
|
cause: e,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async listKeys(_storePath, namespace) {
|
||||||
|
try {
|
||||||
|
const manager = BrowserStoreManager.new()
|
||||||
|
return E.right(await manager.listKeys(namespace))
|
||||||
|
} catch (e) {
|
||||||
|
return E.left({
|
||||||
|
kind: "storage",
|
||||||
|
message: e instanceof Error ? e.message : "Unknown error",
|
||||||
|
cause: e,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async watch(_storePath, namespace, key) {
|
||||||
|
const manager = BrowserStoreManager.new()
|
||||||
|
return manager.watch(namespace, key)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
import { v1 } from './v/1'
|
import { v1 } from "./v/1"
|
||||||
|
|
||||||
export type {
|
export type { StoreV1 } from "./v/1"
|
||||||
StoreV1,
|
|
||||||
} from './v/1'
|
|
||||||
|
|
||||||
export const VERSIONS = {
|
export const VERSIONS = {
|
||||||
v1,
|
v1,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export const latest = v1
|
export const latest = v1
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,27 @@
|
||||||
import type { VersionedAPI } from '@type/versioning'
|
import type { VersionedAPI } from "@type/versioning"
|
||||||
import * as E from 'fp-ts/Either'
|
import * as E from "fp-ts/Either"
|
||||||
import { z } from 'zod'
|
import { z } from "zod"
|
||||||
|
|
||||||
export type StoreCapability =
|
export type StoreCapability =
|
||||||
| 'permanent'
|
| "permanent"
|
||||||
| 'temporary'
|
| "temporary"
|
||||||
| 'structured'
|
| "structured"
|
||||||
| 'sync'
|
| "sync"
|
||||||
| 'watch'
|
| "watch"
|
||||||
| 'secure'
|
| "secure"
|
||||||
| 'namespace'
|
| "namespace"
|
||||||
|
|
||||||
export type StoreError =
|
export type StoreError =
|
||||||
| { kind: 'not_found'; message: string }
|
| { kind: "not_found"; message: string }
|
||||||
| { kind: 'permission'; message: string }
|
| { kind: "permission"; message: string }
|
||||||
| { kind: 'quota'; message: string }
|
| { kind: "quota"; message: string }
|
||||||
| { kind: 'version'; message: string }
|
| { kind: "version"; message: string }
|
||||||
| { kind: 'parse'; message: string; cause?: unknown }
|
| { kind: "parse"; message: string; cause?: unknown }
|
||||||
| { kind: 'storage'; message: string; cause?: unknown }
|
| { kind: "storage"; message: string; cause?: unknown }
|
||||||
| { kind: 'encrypt'; message: string; cause?: unknown }
|
| { kind: "encrypt"; message: string; cause?: unknown }
|
||||||
|
|
||||||
export interface StoreFile {
|
export interface StoreFile {
|
||||||
include?: boolean,
|
include?: boolean
|
||||||
|
|
||||||
name: string
|
name: string
|
||||||
size: number
|
size: number
|
||||||
|
|
@ -48,13 +48,15 @@ export interface StoreEvents {
|
||||||
export const StoreMetadataSchema = z.object({
|
export const StoreMetadataSchema = z.object({
|
||||||
version: z.number(),
|
version: z.number(),
|
||||||
lastUpdated: z.string().datetime(),
|
lastUpdated: z.string().datetime(),
|
||||||
namespaces: z.record(z.object({
|
namespaces: z.record(
|
||||||
name: z.string(),
|
z.object({
|
||||||
version: z.number(),
|
name: z.string(),
|
||||||
createdAt: z.string().datetime(),
|
version: z.number(),
|
||||||
updatedAt: z.string().datetime(),
|
createdAt: z.string().datetime(),
|
||||||
keys: z.array(z.string())
|
updatedAt: z.string().datetime(),
|
||||||
}))
|
keys: z.array(z.string()),
|
||||||
|
})
|
||||||
|
),
|
||||||
})
|
})
|
||||||
|
|
||||||
export type StoreMetadata = z.infer<typeof StoreMetadataSchema>
|
export type StoreMetadata = z.infer<typeof StoreMetadataSchema>
|
||||||
|
|
@ -67,16 +69,19 @@ export const StoredDataSchema = z.object({
|
||||||
namespace: z.string(),
|
namespace: z.string(),
|
||||||
encrypted: z.boolean().optional(),
|
encrypted: z.boolean().optional(),
|
||||||
compressed: z.boolean().optional(),
|
compressed: z.boolean().optional(),
|
||||||
ttl: z.number().optional()
|
ttl: z.number().optional(),
|
||||||
}),
|
}),
|
||||||
data: z.unknown()
|
data: z.unknown(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export type StoredData = z.infer<typeof StoredDataSchema>
|
export type StoredData = z.infer<typeof StoredDataSchema>
|
||||||
|
|
||||||
export interface StoreEventEmitter<T> {
|
export interface StoreEventEmitter<T> {
|
||||||
on<K extends keyof T>(event: K, handler: (payload: T[K]) => void): () => void
|
on<K extends keyof T>(event: K, handler: (payload: T[K]) => void): () => void
|
||||||
once<K extends keyof T>(event: K, handler: (payload: T[K]) => void): () => void
|
once<K extends keyof T>(
|
||||||
|
event: K,
|
||||||
|
handler: (payload: T[K]) => void
|
||||||
|
): () => void
|
||||||
off<K extends keyof T>(event: K, handler: (payload: T[K]) => void): void
|
off<K extends keyof T>(event: K, handler: (payload: T[K]) => void): void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,34 +90,64 @@ export interface StoreV1 {
|
||||||
readonly capabilities: Set<StoreCapability>
|
readonly capabilities: Set<StoreCapability>
|
||||||
|
|
||||||
init(storePath: string): Promise<E.Either<StoreError, void>>
|
init(storePath: string): Promise<E.Either<StoreError, void>>
|
||||||
set(storePath: string, namespace: string, key: string, value: unknown, options?: StorageOptions): Promise<E.Either<StoreError, void>>
|
set(
|
||||||
get<T>(storePath: string, namespace: string, key: string): Promise<E.Either<StoreError, T | undefined>>
|
storePath: string,
|
||||||
remove(storePath: string, namespace: string, key: string): Promise<E.Either<StoreError, boolean>>
|
namespace: string,
|
||||||
clear(storePath: string, namespace?: string): Promise<E.Either<StoreError, void>>
|
key: string,
|
||||||
has(storePath: string, namespace: string, key: string): Promise<E.Either<StoreError, boolean>>
|
value: unknown,
|
||||||
|
options?: StorageOptions
|
||||||
|
): Promise<E.Either<StoreError, void>>
|
||||||
|
get<T>(
|
||||||
|
storePath: string,
|
||||||
|
namespace: string,
|
||||||
|
key: string
|
||||||
|
): Promise<E.Either<StoreError, T | undefined>>
|
||||||
|
remove(
|
||||||
|
storePath: string,
|
||||||
|
namespace: string,
|
||||||
|
key: string
|
||||||
|
): Promise<E.Either<StoreError, boolean>>
|
||||||
|
clear(
|
||||||
|
storePath: string,
|
||||||
|
namespace?: string
|
||||||
|
): Promise<E.Either<StoreError, void>>
|
||||||
|
has(
|
||||||
|
storePath: string,
|
||||||
|
namespace: string,
|
||||||
|
key: string
|
||||||
|
): Promise<E.Either<StoreError, boolean>>
|
||||||
listNamespaces(storePath: string): Promise<E.Either<StoreError, string[]>>
|
listNamespaces(storePath: string): Promise<E.Either<StoreError, string[]>>
|
||||||
listKeys(storePath: string, namespace: string): Promise<E.Either<StoreError, string[]>>
|
listKeys(
|
||||||
watch(storePath: string, namespace: string, key: string): Promise<StoreEventEmitter<StoreEvents>>
|
storePath: string,
|
||||||
|
namespace: string
|
||||||
|
): Promise<E.Either<StoreError, string[]>>
|
||||||
|
watch(
|
||||||
|
storePath: string,
|
||||||
|
namespace: string,
|
||||||
|
key: string
|
||||||
|
): Promise<StoreEventEmitter<StoreEvents>>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const v1: VersionedAPI<StoreV1> = {
|
export const v1: VersionedAPI<StoreV1> = {
|
||||||
version: { major: 1, minor: 0, patch: 0 },
|
version: { major: 1, minor: 0, patch: 0 },
|
||||||
api: {
|
api: {
|
||||||
id: 'default',
|
id: "default",
|
||||||
capabilities: new Set(),
|
capabilities: new Set(),
|
||||||
|
|
||||||
init: async () => E.left({ kind: 'version', message: 'Not implemented' }),
|
init: async () => E.left({ kind: "version", message: "Not implemented" }),
|
||||||
set: async () => E.left({ kind: 'version', message: 'Not implemented' }),
|
set: async () => E.left({ kind: "version", message: "Not implemented" }),
|
||||||
get: async () => E.left({ kind: 'version', message: 'Not implemented' }),
|
get: async () => E.left({ kind: "version", message: "Not implemented" }),
|
||||||
remove: async () => E.left({ kind: 'version', message: 'Not implemented' }),
|
remove: async () => E.left({ kind: "version", message: "Not implemented" }),
|
||||||
clear: async () => E.left({ kind: 'version', message: 'Not implemented' }),
|
clear: async () => E.left({ kind: "version", message: "Not implemented" }),
|
||||||
has: async () => E.left({ kind: 'version', message: 'Not implemented' }),
|
has: async () => E.left({ kind: "version", message: "Not implemented" }),
|
||||||
listNamespaces: async () => E.left({ kind: 'version', message: 'Not implemented' }),
|
listNamespaces: async () =>
|
||||||
listKeys: async () => E.left({ kind: 'version', message: 'Not implemented' }),
|
E.left({ kind: "version", message: "Not implemented" }),
|
||||||
|
listKeys: async () =>
|
||||||
|
E.left({ kind: "version", message: "Not implemented" }),
|
||||||
watch: async () => ({
|
watch: async () => ({
|
||||||
on: () => () => {},
|
on: () => () => {},
|
||||||
once: () => () => {},
|
once: () => () => {},
|
||||||
off: () => {}
|
off: () => {},
|
||||||
})
|
}),
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import type { Version } from '@type/versioning'
|
import type { Version } from "@type/versioning"
|
||||||
|
|
||||||
export function checkCapability(required: Version, available: Version): boolean {
|
export function checkCapability(
|
||||||
|
required: Version,
|
||||||
|
available: Version
|
||||||
|
): boolean {
|
||||||
if (available.major !== required.major) return false
|
if (available.major !== required.major) return false
|
||||||
if (available.minor < required.minor) return false
|
if (available.minor < required.minor) return false
|
||||||
return true
|
return true
|
||||||
|
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
/* eslint-env node */
|
|
||||||
require("@rushstack/eslint-patch/modern-module-resolution")
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
env: {
|
|
||||||
browser: true,
|
|
||||||
node: true,
|
|
||||||
jest: true,
|
|
||||||
},
|
|
||||||
parserOptions: {
|
|
||||||
sourceType: "module",
|
|
||||||
requireConfigFile: false,
|
|
||||||
ecmaFeatures: {
|
|
||||||
jsx: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
extends: [
|
|
||||||
"@vue/typescript/recommended",
|
|
||||||
"plugin:vue/recommended",
|
|
||||||
"plugin:prettier/recommended",
|
|
||||||
],
|
|
||||||
ignorePatterns: [
|
|
||||||
"static/**/*",
|
|
||||||
"./helpers/backend/graphql.ts",
|
|
||||||
"**/*.d.ts",
|
|
||||||
"types/**/*",
|
|
||||||
],
|
|
||||||
plugins: ["vue", "prettier"],
|
|
||||||
// add your custom rules here
|
|
||||||
rules: {
|
|
||||||
semi: [2, "never"],
|
|
||||||
"import/named": "off", // because, named import issue with typescript see: https://github.com/typescript-eslint/typescript-eslint/issues/154
|
|
||||||
"no-console": "off",
|
|
||||||
"no-debugger": process.env.HOPP_LINT_FOR_PROD === "true" ? "error" : "warn",
|
|
||||||
"prettier/prettier":
|
|
||||||
process.env.HOPP_LINT_FOR_PROD === "true" ? "error" : "warn",
|
|
||||||
"vue/multi-word-component-names": "off",
|
|
||||||
"vue/no-side-effects-in-computed-properties": "off",
|
|
||||||
"import/no-named-as-default": "off",
|
|
||||||
"import/no-named-as-default-member": "off",
|
|
||||||
"@typescript-eslint/no-unused-vars": [
|
|
||||||
process.env.HOPP_LINT_FOR_PROD === "true" ? "error" : "warn",
|
|
||||||
{
|
|
||||||
"argsIgnorePattern": "^_",
|
|
||||||
"varsIgnorePattern": "^_",
|
|
||||||
"caughtErrorsIgnorePattern": "^_"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"@typescript-eslint/no-non-null-assertion": "off",
|
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
|
||||||
"import/default": "off",
|
|
||||||
"no-undef": "off",
|
|
||||||
// localStorage block
|
|
||||||
"no-restricted-globals": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
name: "localStorage",
|
|
||||||
message:
|
|
||||||
"Do not use 'localStorage' directly. Please use localpersistence.ts functions or stores",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
// window.localStorage block
|
|
||||||
"no-restricted-syntax": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
selector: "CallExpression[callee.object.property.name='localStorage']",
|
|
||||||
message:
|
|
||||||
"Do not use 'localStorage' directly. Please use localpersistence.ts functions or stores",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
85
packages/hoppscotch-selfhost-web/eslint.config.mjs
Normal file
85
packages/hoppscotch-selfhost-web/eslint.config.mjs
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
import pluginVue from "eslint-plugin-vue"
|
||||||
|
import {
|
||||||
|
defineConfigWithVueTs,
|
||||||
|
vueTsConfigs,
|
||||||
|
} from "@vue/eslint-config-typescript"
|
||||||
|
import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended"
|
||||||
|
import globals from "globals"
|
||||||
|
|
||||||
|
export default defineConfigWithVueTs(
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
"static/**",
|
||||||
|
"src/api/generated/**",
|
||||||
|
"**/*.d.ts",
|
||||||
|
"types/**",
|
||||||
|
"dist/**",
|
||||||
|
"node_modules/**",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
pluginVue.configs["flat/recommended"],
|
||||||
|
vueTsConfigs.recommended,
|
||||||
|
eslintPluginPrettierRecommended,
|
||||||
|
{
|
||||||
|
files: ["**/*.ts", "**/*.js", "**/*.vue"],
|
||||||
|
linterOptions: {
|
||||||
|
reportUnusedDisableDirectives: false,
|
||||||
|
},
|
||||||
|
languageOptions: {
|
||||||
|
sourceType: "module",
|
||||||
|
ecmaVersion: "latest",
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.node,
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
requireConfigFile: false,
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
semi: [2, "never"],
|
||||||
|
"import/named": "off",
|
||||||
|
"no-console": "off",
|
||||||
|
"no-debugger": process.env.HOPP_LINT_FOR_PROD === "true" ? "error" : "warn",
|
||||||
|
"prettier/prettier":
|
||||||
|
process.env.HOPP_LINT_FOR_PROD === "true" ? "error" : "warn",
|
||||||
|
"vue/multi-word-component-names": "off",
|
||||||
|
"vue/no-side-effects-in-computed-properties": "off",
|
||||||
|
"import/no-named-as-default": "off",
|
||||||
|
"import/no-named-as-default-member": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": [
|
||||||
|
process.env.HOPP_LINT_FOR_PROD === "true" ? "error" : "warn",
|
||||||
|
{
|
||||||
|
argsIgnorePattern: "^_",
|
||||||
|
varsIgnorePattern: "^_",
|
||||||
|
caughtErrorsIgnorePattern: "^_",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@typescript-eslint/no-unused-expressions": "off",
|
||||||
|
"@typescript-eslint/no-non-null-assertion": "off",
|
||||||
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-function-type": "off",
|
||||||
|
"import/default": "off",
|
||||||
|
"no-undef": "off",
|
||||||
|
"no-restricted-globals": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
name: "localStorage",
|
||||||
|
message:
|
||||||
|
"Do not use 'localStorage' directly. Please use the PersistenceService",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"no-restricted-syntax": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
selector: "CallExpression[callee.object.property.name='localStorage']",
|
||||||
|
message:
|
||||||
|
"Do not use 'localStorage' directly. Please use the PersistenceService",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
@ -9,9 +9,9 @@
|
||||||
"dev": "pnpm exec npm-run-all -p -l dev:*",
|
"dev": "pnpm exec npm-run-all -p -l dev:*",
|
||||||
"build": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build",
|
"build": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"lint": "eslint src --ext .ts,.js,.vue --ignore-path .gitignore .",
|
"lint": "eslint src",
|
||||||
"lint:ts": "vue-tsc --noEmit",
|
"lint:ts": "vue-tsc --noEmit",
|
||||||
"lintfix": "eslint --fix src --ext .ts,.js,.vue --ignore-path .gitignore .",
|
"lintfix": "eslint --fix src",
|
||||||
"prod-lint": "cross-env HOPP_LINT_FOR_PROD=true pnpm run lint",
|
"prod-lint": "cross-env HOPP_LINT_FOR_PROD=true pnpm run lint",
|
||||||
"generate": "pnpm run build",
|
"generate": "pnpm run build",
|
||||||
"do-dev": "pnpm run dev",
|
"do-dev": "pnpm run dev",
|
||||||
|
|
@ -51,6 +51,8 @@
|
||||||
"zod": "3.25.32"
|
"zod": "3.25.32"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eslint/eslintrc": "3.3.3",
|
||||||
|
"@eslint/js": "9.39.2",
|
||||||
"@graphql-codegen/add": "6.0.0",
|
"@graphql-codegen/add": "6.0.0",
|
||||||
"@graphql-codegen/cli": "6.1.0",
|
"@graphql-codegen/cli": "6.1.0",
|
||||||
"@graphql-codegen/typed-document-node": "6.1.5",
|
"@graphql-codegen/typed-document-node": "6.1.5",
|
||||||
|
|
@ -66,13 +68,14 @@
|
||||||
"@typescript-eslint/parser": "8.50.0",
|
"@typescript-eslint/parser": "8.50.0",
|
||||||
"@vitejs/plugin-legacy": "7.2.1",
|
"@vitejs/plugin-legacy": "7.2.1",
|
||||||
"@vitejs/plugin-vue": "6.0.3",
|
"@vitejs/plugin-vue": "6.0.3",
|
||||||
"@vue/eslint-config-typescript": "13.0.0",
|
"@vue/eslint-config-typescript": "14.6.0",
|
||||||
"autoprefixer": "10.4.23",
|
"autoprefixer": "10.4.23",
|
||||||
"cross-env": "10.1.0",
|
"cross-env": "10.1.0",
|
||||||
"dotenv": "17.2.3",
|
"dotenv": "17.2.3",
|
||||||
"eslint": "8.57.0",
|
"eslint": "9.39.2",
|
||||||
"eslint-plugin-prettier": "5.5.4",
|
"eslint-plugin-prettier": "5.5.4",
|
||||||
"eslint-plugin-vue": "10.6.2",
|
"eslint-plugin-vue": "10.6.2",
|
||||||
|
"globals": "16.5.0",
|
||||||
"npm-run-all": "4.1.5",
|
"npm-run-all": "4.1.5",
|
||||||
"postcss": "8.5.6",
|
"postcss": "8.5.6",
|
||||||
"prettier-plugin-tailwindcss": "0.7.1",
|
"prettier-plugin-tailwindcss": "0.7.1",
|
||||||
|
|
|
||||||
|
|
@ -139,7 +139,7 @@ async function getInitialUserDetails(): Promise<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { error: "auth/cookies_not_found" }
|
return { error: "auth/cookies_not_found" }
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
return { error: "auth/cookies_not_found" }
|
return { error: "auth/cookies_not_found" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -254,7 +254,7 @@ async function refreshToken() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return isSuccessful
|
return isSuccessful
|
||||||
} catch (err) {
|
} catch (_err) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -167,7 +167,7 @@ async function refreshToken() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return isSuccessful
|
return isSuccessful
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -394,7 +394,7 @@ export const def: AuthPlatformDef = {
|
||||||
|
|
||||||
// axios automatically throws on error status codes, so if we reach here, it was successful
|
// axios automatically throws on error status codes, so if we reach here, it was successful
|
||||||
return !!response.data.isValid
|
return !!response.data.isValid
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
2141
pnpm-lock.yaml
2141
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue