From 513403ce6ce5d7fa51d9802c911edfa8a5c933c0 Mon Sep 17 00:00:00 2001 From: Rokibul Islam Date: Thu, 16 Nov 2023 23:48:08 +0530 Subject: [PATCH 1/5] feat(backend): add desktop auth deeplink callback --- .env.example | 1 + .../src/auth/auth.controller.ts | 17 +++++++++++------ packages/hoppscotch-backend/src/auth/helper.ts | 18 ++++++++++++++++++ 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/.env.example b/.env.example index 56a4e00168..e98effed7a 100644 --- a/.env.example +++ b/.env.example @@ -14,6 +14,7 @@ SESSION_SECRET='add some secret here' REDIRECT_URL="http://localhost:3000" WHITELISTED_ORIGINS = "http://localhost:3170,http://localhost:3000,http://localhost:3100" VITE_ALLOWED_AUTH_PROVIDERS = GOOGLE,GITHUB,MICROSOFT,EMAIL +DESKTOP_DEEP_LINK_URL=hoppscotch://auth # Google Auth Config GOOGLE_CLIENT_ID="************************************************" diff --git a/packages/hoppscotch-backend/src/auth/auth.controller.ts b/packages/hoppscotch-backend/src/auth/auth.controller.ts index 6375cb584e..2a516a2e36 100644 --- a/packages/hoppscotch-backend/src/auth/auth.controller.ts +++ b/packages/hoppscotch-backend/src/auth/auth.controller.ts @@ -22,6 +22,7 @@ import { RTCookie } from 'src/decorators/rt-cookie.decorator'; import { AuthProvider, authCookieHandler, + authDesktopDeepLinkHandler, authProviderCheck, throwHTTPErr, } from './helper'; @@ -102,12 +103,16 @@ export class AuthController { async googleAuthRedirect(@Request() req, @Res() res) { const authTokens = await this.authService.generateAuthTokens(req.user.uid); if (E.isLeft(authTokens)) throwHTTPErr(authTokens.left); - authCookieHandler( - res, - authTokens.right, - true, - req.authInfo.state.redirect_uri, - ); + if (req.authInfo.state.redirect_uri === 'desktop') { + authDesktopDeepLinkHandler(res, authTokens.right); + } else { + authCookieHandler( + res, + authTokens.right, + true, + req.authInfo.state.redirect_uri, + ); + } } /** diff --git a/packages/hoppscotch-backend/src/auth/helper.ts b/packages/hoppscotch-backend/src/auth/helper.ts index e5e485700a..4ffd84990c 100644 --- a/packages/hoppscotch-backend/src/auth/helper.ts +++ b/packages/hoppscotch-backend/src/auth/helper.ts @@ -83,6 +83,24 @@ export const authCookieHandler = ( return res.status(HttpStatus.OK).redirect(redirectUrl); }; +export const authDesktopDeepLinkHandler = ( + res: Response, + authTokens: AuthTokens, +) => { + const desktopDeepLinkUrl = process.env.DESKTOP_DEEP_LINK_URL; + return res + .status(HttpStatus.OK) + .cookie('test_cookie', 'test_cookie', { + httpOnly: true, + secure: true, + sameSite: 'lax', + maxAge: 1000, + }) + .redirect( + `${desktopDeepLinkUrl}?access_token=${authTokens.access_token}&refresh_token=${authTokens.refresh_token}`, + ); +}; + /** * Decode the cookie header from incoming websocket connects and returns a auth token pair * @param rawCookies cookies from the websocket connection From 4fc29d3f7b77c2178ee6ceba99db69d5781d9722 Mon Sep 17 00:00:00 2001 From: Rokibul Islam Date: Fri, 17 Nov 2023 02:40:25 +0530 Subject: [PATCH 2/5] fix(desktop): fix invalid csp value 'nil' in tauri.conf.json --- packages/hoppscotch-selfhost-desktop/src-tauri/tauri.conf.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hoppscotch-selfhost-desktop/src-tauri/tauri.conf.json b/packages/hoppscotch-selfhost-desktop/src-tauri/tauri.conf.json index 26693ccaa1..8c614bdb85 100644 --- a/packages/hoppscotch-selfhost-desktop/src-tauri/tauri.conf.json +++ b/packages/hoppscotch-selfhost-desktop/src-tauri/tauri.conf.json @@ -49,7 +49,7 @@ "targets": "all" }, "security": { - "csp": "none" + "csp": null }, "updater": { "active": false From 7650b3792f00fe615220cf9b5afdfff8759b83db Mon Sep 17 00:00:00 2001 From: Rokibul Islam Date: Fri, 17 Nov 2023 03:05:04 +0530 Subject: [PATCH 3/5] feat(desktop): add urql fetch & subscription exchanges w/ cookie support --- .../hoppscotch-selfhost-desktop/package.json | 6 + .../src-tauri/Cargo.lock | 70 +++++++- .../src-tauri/Cargo.toml | 1 + .../src-tauri/src/main.rs | 1 + .../src/helpers/GQLClient.ts | 168 ++++++++++++++++++ .../src/helpers/wsWrapper.ts | 154 ++++++++++++++++ .../src/platform/auth.ts | 128 ++++++++----- pnpm-lock.yaml | 52 ++++-- 8 files changed, 517 insertions(+), 63 deletions(-) create mode 100644 packages/hoppscotch-selfhost-desktop/src/helpers/GQLClient.ts create mode 100644 packages/hoppscotch-selfhost-desktop/src/helpers/wsWrapper.ts diff --git a/packages/hoppscotch-selfhost-desktop/package.json b/packages/hoppscotch-selfhost-desktop/package.json index 2da17df871..e42418e827 100644 --- a/packages/hoppscotch-selfhost-desktop/package.json +++ b/packages/hoppscotch-selfhost-desktop/package.json @@ -17,9 +17,11 @@ "@platform/auth": "^0.1.106", "@tauri-apps/api": "^1.3.0", "@tauri-apps/cli": "^1.3.0", + "@urql/core": "^4.1.1", "@vueuse/core": "^10.4.1", "axios": "^0.21.4", "buffer": "^6.0.3", + "cookie": "^0.5.0", "dioc": "workspace:^", "environments.api": "link:@platform/environments/environments.api", "event": "link:@tauri-apps/api/event", @@ -29,10 +31,13 @@ "rxjs": "^7.8.1", "shell": "link:@tauri-apps/api/shell", "stream-browserify": "^3.0.0", + "subscriptions-transport-ws": "^0.11.0", "tauri": "link:@tauri-apps/api/tauri", "tauri-plugin-store-api": "github:tauri-apps/tauri-plugin-store#v1", + "tauri-plugin-websocket-api": "github:tauri-apps/tauri-plugin-websocket#v1", "util": "^0.12.4", "vue": "^3.2.45", + "wonka": "^6.3.4", "workbox-window": "^6.5.4" }, "devDependencies": { @@ -46,6 +51,7 @@ "@graphql-typed-document-node/core": "^3.2.0", "@intlify/vite-plugin-vue-i18n": "^6.0.1", "@rushstack/eslint-patch": "^1.1.4", + "@types/cookie": "^0.5.1", "@types/lodash-es": "^4.17.9", "@types/node": "^18.7.10", "@typescript-eslint/eslint-plugin": "^5.19.0", diff --git a/packages/hoppscotch-selfhost-desktop/src-tauri/Cargo.lock b/packages/hoppscotch-selfhost-desktop/src-tauri/Cargo.lock index 5ce14de2e8..d31795504d 100644 --- a/packages/hoppscotch-selfhost-desktop/src-tauri/Cargo.lock +++ b/packages/hoppscotch-selfhost-desktop/src-tauri/Cargo.lock @@ -549,6 +549,12 @@ dependencies = [ "syn 2.0.26", ] +[[package]] +name = "data-encoding" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" + [[package]] name = "derive_more" version = "0.99.17" @@ -1271,6 +1277,7 @@ dependencies = [ "tauri-build", "tauri-plugin-deep-link", "tauri-plugin-store", + "tauri-plugin-websocket", "tauri-plugin-window-state", "url", "windows 0.51.1", @@ -2797,6 +2804,17 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.7" @@ -3192,7 +3210,7 @@ dependencies = [ [[package]] name = "tauri-plugin-store" version = "0.0.0" -source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#5b814f56e6368fdec46c4ddb04a07e0923ff995a" +source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#642a195d34ec5bf6bbc780dde9c02fca33e1bd00" dependencies = [ "log", "serde", @@ -3201,6 +3219,22 @@ dependencies = [ "thiserror", ] +[[package]] +name = "tauri-plugin-websocket" +version = "0.0.0" +source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#642a195d34ec5bf6bbc780dde9c02fca33e1bd00" +dependencies = [ + "futures-util", + "log", + "rand 0.8.5", + "serde", + "serde_json", + "tauri", + "thiserror", + "tokio", + "tokio-tungstenite", +] + [[package]] name = "tauri-plugin-window-state" version = "0.1.0" @@ -3429,6 +3463,20 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" +dependencies = [ + "futures-util", + "log", + "native-tls", + "tokio", + "tokio-native-tls", + "tungstenite", +] + [[package]] name = "tokio-util" version = "0.7.8" @@ -3569,6 +3617,26 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +[[package]] +name = "tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "native-tls", + "rand 0.8.5", + "sha1", + "thiserror", + "url", + "utf-8", +] + [[package]] name = "typenum" version = "1.16.0" diff --git a/packages/hoppscotch-selfhost-desktop/src-tauri/Cargo.toml b/packages/hoppscotch-selfhost-desktop/src-tauri/Cargo.toml index 12a43c3fc9..420ed30756 100644 --- a/packages/hoppscotch-selfhost-desktop/src-tauri/Cargo.toml +++ b/packages/hoppscotch-selfhost-desktop/src-tauri/Cargo.toml @@ -24,6 +24,7 @@ tauri = { version = "1.4.1", features = [ ] } tauri-plugin-store = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } tauri-plugin-deep-link = { git = "https://github.com/FabianLars/tauri-plugin-deep-link", branch = "main" } +tauri-plugin-websocket = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } tauri-plugin-window-state = "0.1.0" reqwest = "0.11.20" serde_json = "1.0.107" diff --git a/packages/hoppscotch-selfhost-desktop/src-tauri/src/main.rs b/packages/hoppscotch-selfhost-desktop/src-tauri/src/main.rs index 34171068bc..46c8ea17c4 100644 --- a/packages/hoppscotch-selfhost-desktop/src-tauri/src/main.rs +++ b/packages/hoppscotch-selfhost-desktop/src-tauri/src/main.rs @@ -21,6 +21,7 @@ fn main() { tauri_plugin_deep_link::prepare("io.hoppscotch.desktop"); tauri::Builder::default() + .plugin(tauri_plugin_websocket::init()) .plugin(tauri_plugin_window_state::Builder::default().build()) .plugin(tauri_plugin_store::Builder::default().build()) .setup(|app| { diff --git a/packages/hoppscotch-selfhost-desktop/src/helpers/GQLClient.ts b/packages/hoppscotch-selfhost-desktop/src/helpers/GQLClient.ts new file mode 100644 index 0000000000..30947a1b97 --- /dev/null +++ b/packages/hoppscotch-selfhost-desktop/src/helpers/GQLClient.ts @@ -0,0 +1,168 @@ +import { ref } from "vue" +import { makeResult, makeErrorResult, Exchange, Operation } from "@urql/core" +import { makeFetchBody, makeFetchOptions } from "@urql/core/internal" +import { filter, make, merge, mergeMap, pipe, takeUntil, map } from "wonka" +import { gqlClientError$ } from "@hoppscotch/common/helpers/backend/GQLClient" +import { Store } from "tauri-plugin-store-api" +import { Body, getClient } from "@tauri-apps/api/http" +import { parse, serialize } from "cookie" +import { SubscriptionClient } from "subscriptions-transport-ws" +import { platform } from "@hoppscotch/common/platform" +import WSWrapper from "./wsWrapper" + +const APP_DATA_PATH = "~/.hopp-desktop-app-data.dat" + +export async function addCookieToFetchHeaders( + store: Store, + headers: HeadersInit = {} +) { + try { + const accessToken = await store.get<{ value: string }>("access_token") + const refreshToken = await store.get<{ value: string }>("refresh_token") + + if (accessToken?.value && refreshToken?.value) { + // Assert headers as an indexable type + const headersIndexable = headers as { [key: string]: string } + const existingCookies = parse(headersIndexable["Cookie"] || "") + + if (!existingCookies.access_token) { + existingCookies.access_token = accessToken.value + } + if (!existingCookies.refresh_token) { + existingCookies.refresh_token = refreshToken.value + } + + // Serialize the cookies back into the headers + const serializedCookies = Object.entries(existingCookies) + .map(([name, value]) => serialize(name, value)) + .join("; ") + headersIndexable["Cookie"] = serializedCookies + } + + return headers + } catch (error) { + console.error("error while injecting cookie") + } +} + +function createHttpSource(operation: Operation, store: Store) { + return make(({ next, complete }) => { + getClient().then(async (httpClient) => { + const fetchOptions = makeFetchOptions(operation) + let headers = fetchOptions.headers + headers = await addCookieToFetchHeaders(store, headers) + + const fetchBody = makeFetchBody(operation) + httpClient + .post(operation.context.url, Body.json(fetchBody), { + headers, + }) + .then((result) => { + next(result.data) + complete() + }) + .catch((error) => { + next(makeErrorResult(operation, error)) + complete() + }) + }) + return () => {} + }) +} + +export const tauriGQLFetchExchange = + (store: Store): Exchange => + ({ forward }) => + (ops$) => { + const subscriptionResults$ = pipe( + ops$, + filter((op) => op.kind === "query" || op.kind === "mutation"), + mergeMap((operation) => { + const { key, context } = operation + + const teardown$ = pipe( + ops$, + filter((op: Operation) => op.kind === "teardown" && op.key === key) + ) + + const source = createHttpSource(operation, store) + + return pipe( + source, + takeUntil(teardown$), + map((result) => makeResult(operation, result as any)) + ) + }) + ) + + const forward$ = pipe( + ops$, + filter( + (op: Operation) => op.kind === "teardown" || op.kind != "subscription" + ), + forward + ) + + return merge([subscriptionResults$, forward$]) + } + +const createSubscriptionClient = () => { + return new SubscriptionClient( + import.meta.env.VITE_BACKEND_WS_URL, + { + reconnect: true, + connectionParams: () => platform.auth.getBackendHeaders(), + connectionCallback(error) { + if (error?.length > 0) { + gqlClientError$.next({ + type: "SUBSCRIPTION_CONN_CALLBACK_ERR_REPORT", + errors: error, + }) + } + }, + wsOptionArguments: [ + { + store: new Store(APP_DATA_PATH), + }, + ], + }, + WSWrapper + ) +} + +let subscriptionClient: SubscriptionClient | null + +const isBackendGQLEventAdded = ref(false) + +const resetSubscriptionClient = () => { + if (subscriptionClient) { + subscriptionClient.close() + } + subscriptionClient = createSubscriptionClient() + if (!isBackendGQLEventAdded.value) { + subscriptionClient.onConnected(() => { + platform.auth.onBackendGQLClientShouldReconnect(() => { + const currentUser = platform.auth.getCurrentUser() + + if (currentUser && subscriptionClient) { + subscriptionClient?.client?.close() + } + + if (currentUser && !subscriptionClient) { + resetSubscriptionClient() + } + + if (!currentUser && subscriptionClient) { + subscriptionClient.close() + resetSubscriptionClient() + } + }) + }) + isBackendGQLEventAdded.value = true + } +} + +export const getSubscriptionClient = () => { + if (!subscriptionClient) resetSubscriptionClient() + return subscriptionClient +} diff --git a/packages/hoppscotch-selfhost-desktop/src/helpers/wsWrapper.ts b/packages/hoppscotch-selfhost-desktop/src/helpers/wsWrapper.ts new file mode 100644 index 0000000000..27340a9c93 --- /dev/null +++ b/packages/hoppscotch-selfhost-desktop/src/helpers/wsWrapper.ts @@ -0,0 +1,154 @@ +import { Store } from "tauri-plugin-store-api" +import TauriWebSocket, { + Message, + ConnectionConfig, +} from "tauri-plugin-websocket-api" +import { addCookieToFetchHeaders } from "./GQLClient" + +/** + * This is a wrapper around tauri-plugin-websocket-api with cookie injection support. This is required because + * subscriptions-transport-ws client expects a custom websocket implementation in the shape of native browser WebSocket. + */ + +export default class WebSocketWrapper extends EventTarget implements WebSocket { + public client: TauriWebSocket | undefined + private tauriWebSocketConfig: + | (ConnectionConfig & { store: Store }) + | undefined + private isConnected: boolean = false + binaryType: BinaryType = "blob" + extensions = "" + onclose: ((this: WebSocket, ev: CloseEvent) => any) | null = null + onerror: ((this: WebSocket, ev: Event) => any) | null = null + onmessage: ((this: WebSocket, ev: MessageEvent) => any) | null = null + onopen: ((this: WebSocket, ev: Event) => any) | null = null + protocol = "" + url: string + + public static readonly CONNECTING = 0 + public static readonly OPEN = 1 + public static readonly CLOSING = 2 + public static readonly CLOSED = 3 + + readonly CONNECTING = 0 + readonly OPEN = 1 + readonly CLOSING = 2 + readonly CLOSED = 3 + + constructor( + url: string, + protocols?: string | string[], + config?: ConnectionConfig & { store: Store } + ) { + super() + this.url = url + this.tauriWebSocketConfig = config + this.setup() + } + + private async setup() { + if (this.tauriWebSocketConfig?.store) { + const headersStringified = + this.tauriWebSocketConfig.headers || ("{}" as any) + let headers = JSON.parse(headersStringified) + headers = await addCookieToFetchHeaders( + this.tauriWebSocketConfig.store, + headers + ) + this.tauriWebSocketConfig = { + ...this.tauriWebSocketConfig, + headers, + } + } + this.client = await TauriWebSocket.connect(this.url, { + headers: { + "sec-websocket-protocol": "graphql-ws", + ...this.tauriWebSocketConfig?.headers, + }, + }).catch((error) => { + this.isConnected = false + if (this.onerror) { + this.onerror(new Event("error")) + } + throw new Error(error) + }) + + this.isConnected = true + + this.client.addListener(this.handleMessage.bind(this)) + if (this.onopen) { + this.onopen(new Event("open")) + } + } + + get readyState(): number { + return this.client + ? this.isConnected + ? this.OPEN + : this.CLOSED + : this.CONNECTING + } + + get bufferedAmount(): number { + // TODO implement + return 0 + } + + close(code?: number, reason?: string): void { + this.client?.disconnect().then(() => { + if (this.onclose) { + this.onclose(new CloseEvent("close")) + } + }) + } + + send(data: string | ArrayBufferLike | Blob | ArrayBufferView) { + if ( + typeof data === "string" || + data instanceof ArrayBuffer || + data instanceof Blob + ) { + this.client?.send(data as string).catch((error) => { + console.error("error while sending data", data) + if (this.onerror) { + this.onerror(new Event("error")) + } + }) + } else { + // TODO implement, drop the record for now + console.warn( + "WebSocketWrapper.send() not implemented for non-string data" + ) + } + } + + private handleMessage(message: Message): void { + switch (message.type) { + case "Close": { + if (this.onclose) { + this.onclose(new CloseEvent("close")) + } + return + } + case "Ping": { + this.client?.send("Pong").catch((error) => { + console.error("error while sending Pong data", message) + if (this.onerror) { + this.onerror(new Event("error")) + } + }) + return + } + default: { + if (this.onmessage) { + this.onmessage( + new MessageEvent("message", { + data: message.data, + origin: this.url, + }) + ) + } + } + } + } +} diff --git a/packages/hoppscotch-selfhost-desktop/src/platform/auth.ts b/packages/hoppscotch-selfhost-desktop/src/platform/auth.ts index d1094625c0..840db4149c 100644 --- a/packages/hoppscotch-selfhost-desktop/src/platform/auth.ts +++ b/packages/hoppscotch-selfhost-desktop/src/platform/auth.ts @@ -11,19 +11,24 @@ import { setLocalConfig, } from "@hoppscotch/common/newstore/localpersistence" import { Ref, ref, watch } from "vue" -import { open } from '@tauri-apps/api/shell' -import { Body, getClient } from '@tauri-apps/api/http' -import { listen } from '@tauri-apps/api/event' -import { Store } from "tauri-plugin-store-api"; +import { open } from "@tauri-apps/api/shell" +import { Body, getClient } from "@tauri-apps/api/http" +import { listen } from "@tauri-apps/api/event" +import { Store } from "tauri-plugin-store-api" +import { subscriptionExchange } from "@urql/core" +import { + getSubscriptionClient, + tauriGQLFetchExchange, +} from "../helpers/GQLClient" export const authEvents$ = new Subject() const currentUser$ = new BehaviorSubject(null) export const probableUser$ = new BehaviorSubject(null) -const APP_DATA_PATH = "~/.hopp-desktop-app-data.dat" +export const APP_DATA_PATH = "~/.hopp-desktop-app-data.dat" async function logout() { - let client = await getClient(); + let client = await getClient() await client.get(`${import.meta.env.VITE_BACKEND_API_URL}/auth/logout`) const store = new Store(APP_DATA_PATH) @@ -33,19 +38,27 @@ async function logout() { } async function signInUserWithGithubFB() { - await open(`${import.meta.env.VITE_BACKEND_API_URL}/auth/github?redirect_uri=desktop`); + await open( + `${import.meta.env.VITE_BACKEND_API_URL}/auth/github?redirect_uri=desktop` + ) } async function signInUserWithGoogleFB() { - await open(`${import.meta.env.VITE_BACKEND_API_URL}/auth/google?redirect_uri=desktop`); + await open( + `${import.meta.env.VITE_BACKEND_API_URL}/auth/google?redirect_uri=desktop` + ) } async function signInUserWithMicrosoftFB() { - await open(`${import.meta.env.VITE_BACKEND_API_URL}/auth/microsoft?redirect_uri=desktop`); + await open( + `${ + import.meta.env.VITE_BACKEND_API_URL + }/auth/microsoft?redirect_uri=desktop` + ) } async function getInitialUserDetails() { - const store = new Store(APP_DATA_PATH); + const store = new Store(APP_DATA_PATH) try { const accessToken = await store.get("access_token") @@ -60,20 +73,23 @@ async function getInitialUserDetails() { isAdmin createdOn } - }`} + }`, + } - let res = await client.post(`${import.meta.env.VITE_BACKEND_GQL_URL}`, - Body.json(body), { - headers: { - "Cookie": `access_token=${accessToken.value}`, + let res = await client.post( + `${import.meta.env.VITE_BACKEND_GQL_URL}`, + Body.json(body), + { + headers: { + Cookie: `access_token=${accessToken.value}`, + }, } - } ) return res.data } catch (error) { let res = { - error: "auth/cookies_not_found" + error: "auth/cookies_not_found", } return res @@ -149,14 +165,17 @@ async function setInitialUser() { } async function refreshToken() { - const store = new Store(APP_DATA_PATH); + const store = new Store(APP_DATA_PATH) try { const refreshToken = await store.get("refresh_token") let client = await getClient() - let res = await client.get(`${import.meta.env.VITE_BACKEND_API_URL}/auth/refresh`, { - headers: { "Cookie": `refresh_token=${refreshToken.value}` } - }) + let res = await client.get( + `${import.meta.env.VITE_BACKEND_API_URL}/auth/refresh`, + { + headers: { Cookie: `refresh_token=${refreshToken.value}` }, + } + ) setAuthCookies(res.rawHeaders) @@ -175,10 +194,10 @@ async function refreshToken() { } async function sendMagicLink(email: string) { - const client = await getClient(); - let url = `${import.meta.env.VITE_BACKEND_API_URL}/auth/signin?origin=desktop`; + const client = await getClient() + let url = `${import.meta.env.VITE_BACKEND_API_URL}/auth/signin?origin=desktop` - const res = await client.post(url, Body.json({ email })); + const res = await client.post(url, Body.json({ email })) if (res.data && res.data.deviceIdentifier) { setLocalConfig("deviceIdentifier", res.data.deviceIdentifier) @@ -190,32 +209,30 @@ async function sendMagicLink(email: string) { } async function setAuthCookies(rawHeaders: Array) { - let cookies = rawHeaders['set-cookie'].join("|") + let cookies = rawHeaders["set-cookie"].join("|") - const accessTokenMatch = cookies.match(/access_token=([^;]+)/); - const refreshTokenMatch = cookies.match(/refresh_token=([^;]+)/); + const accessTokenMatch = cookies.match(/access_token=([^;]+)/) + const refreshTokenMatch = cookies.match(/refresh_token=([^;]+)/) const store = new Store(APP_DATA_PATH) if (accessTokenMatch) { - const accessToken = accessTokenMatch[1]; + const accessToken = accessTokenMatch[1] await store.set("access_token", { value: accessToken }) } if (refreshTokenMatch) { - const refreshToken = refreshTokenMatch[1]; + const refreshToken = refreshTokenMatch[1] await store.set("refresh_token", { value: refreshToken }) } await store.save() } - export const def: AuthPlatformDef = { getCurrentUserStream: () => currentUser$, getAuthEventsStream: () => authEvents$, getProbableUserStream: () => probableUser$, - getCurrentUser: () => currentUser$.value, getProbableUser: () => probableUser$.value, @@ -224,6 +241,15 @@ export const def: AuthPlatformDef = { }, getGQLClientOptions() { return { + exchanges: [ + subscriptionExchange({ + forwardSubscription(fetchBody) { + const subscriptionClient = getSubscriptionClient() + return subscriptionClient!.request(fetchBody) + }, + }), + tauriGQLFetchExchange(new Store(APP_DATA_PATH)), + ], fetchOptions: { credentials: "include", }, @@ -261,27 +287,30 @@ export const def: AuthPlatformDef = { probableUser$.next(probableUser) await setInitialUser() - await listen('scheme-request-received', async (event: any) => { - let deep_link = event.payload as string; + await listen("scheme-request-received", async (event: any) => { + let deep_link = event.payload as string - const params = new URLSearchParams(deep_link.split('?')[1]); - const accessToken = params.get('access_token'); - const refreshToken = params.get('refresh_token'); - const token = params.get('token'); + const params = new URLSearchParams(deep_link.split("?")[1]) + const accessToken = params.get("access_token") + const refreshToken = params.get("refresh_token") + const token = params.get("token") function isNotNullOrUndefined(x: any) { - return x !== null && x !== undefined; + return x !== null && x !== undefined } - if (isNotNullOrUndefined(accessToken) && isNotNullOrUndefined(refreshToken)) { + if ( + isNotNullOrUndefined(accessToken) && + isNotNullOrUndefined(refreshToken) + ) { const store = new Store(APP_DATA_PATH) - await store.set("access_token", { value: accessToken }); - await store.set("refresh_token", { value: refreshToken } ); + await store.set("access_token", { value: accessToken }) + await store.set("refresh_token", { value: refreshToken }) await store.save() window.location.href = "/" - return; + return } if (isNotNullOrUndefined(token)) { @@ -289,7 +318,7 @@ export const def: AuthPlatformDef = { await this.signInWithEmailLink("", "") await setInitialUser() } - }); + }) }, waitProbableLoginToConfirm() { @@ -337,11 +366,14 @@ export const def: AuthPlatformDef = { let verifyToken = getLocalConfig("verifyToken") - const client = await getClient(); - let res = await client.post(`${import.meta.env.VITE_BACKEND_API_URL}/auth/verify`, Body.json({ - token: verifyToken, - deviceIdentifier - })); + const client = await getClient() + let res = await client.post( + `${import.meta.env.VITE_BACKEND_API_URL}/auth/verify`, + Body.json({ + token: verifyToken, + deviceIdentifier, + }) + ) setAuthCookies(res.rawHeaders) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d3aa33c16e..c762d86e6d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -892,6 +892,9 @@ importers: '@tauri-apps/cli': specifier: ^1.3.0 version: 1.5.6 + '@urql/core': + specifier: ^4.1.1 + version: 4.1.4(graphql@16.8.1) '@vueuse/core': specifier: ^10.4.1 version: 10.5.0(vue@3.3.4) @@ -901,6 +904,9 @@ importers: buffer: specifier: ^6.0.3 version: 6.0.3 + cookie: + specifier: ^0.5.0 + version: 0.5.0 dioc: specifier: workspace:^ version: link:../dioc @@ -928,18 +934,27 @@ importers: stream-browserify: specifier: ^3.0.0 version: 3.0.0 + subscriptions-transport-ws: + specifier: ^0.11.0 + version: 0.11.0(graphql@16.8.1) tauri: specifier: link:@tauri-apps/api/tauri version: link:@tauri-apps/api/tauri tauri-plugin-store-api: specifier: github:tauri-apps/tauri-plugin-store#v1 version: github.com/tauri-apps/tauri-plugin-store/6e19887b1bdea9b921a31993d72396a350731e07 + tauri-plugin-websocket-api: + specifier: github:tauri-apps/tauri-plugin-websocket#v1 + version: github.com/tauri-apps/tauri-plugin-websocket/b55e90e968b9020d1eb8de97c97655bfa002dfd8 util: specifier: ^0.12.4 version: 0.12.5 vue: specifier: ^3.2.45 version: 3.3.4 + wonka: + specifier: ^6.3.4 + version: 6.3.4 workbox-window: specifier: ^6.5.4 version: 6.6.0 @@ -974,6 +989,9 @@ importers: '@rushstack/eslint-patch': specifier: ^1.1.4 version: 1.3.3 + '@types/cookie': + specifier: ^0.5.1 + version: 0.5.1 '@types/lodash-es': specifier: ^4.17.9 version: 4.17.10 @@ -1568,7 +1586,6 @@ packages: optional: true dependencies: graphql: 16.8.1 - dev: true /@aashutoshrathi/word-wrap@1.2.6: resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} @@ -8333,7 +8350,7 @@ packages: dependencies: '@intlify/bundle-utils': 7.4.0(vue-i18n@9.2.2) '@intlify/shared': 9.4.1 - '@rollup/pluginutils': 5.0.3(rollup@2.79.1) + '@rollup/pluginutils': 5.0.3(rollup@3.29.4) '@vue/compiler-sfc': 3.3.4 debug: 4.3.4(supports-color@9.2.2) fast-glob: 3.3.1 @@ -9803,6 +9820,7 @@ packages: estree-walker: 2.0.2 picomatch: 2.3.1 rollup: 2.79.1 + dev: true /@rollup/pluginutils@5.0.3(rollup@3.29.4): resolution: {integrity: sha512-hfllNN4a80rwNQ9QCxhxuHCGHMAvabXqxNdaChUSSadMre7t4iEUI6fFAhBOn/eIYTgYVhBv7vCLsAJ4u3lf3g==} @@ -9817,7 +9835,6 @@ packages: estree-walker: 2.0.2 picomatch: 2.3.1 rollup: 3.29.4 - dev: true /@rushstack/eslint-patch@1.1.4: resolution: {integrity: sha512-LwzQKA4vzIct1zNZzBmRKI9QuNpLgTQMEjsQLf3BXuGYb3QPTP4Yjf6mkdX+X1mYttZ808QpOwAzZjv28kq7DA==} @@ -11534,9 +11551,10 @@ packages: wonka: 6.3.4 transitivePeerDependencies: - graphql + dev: false - /@urql/core@4.1.3(graphql@16.6.0): - resolution: {integrity: sha512-Wapa58olpEJtZzSEuZNDxzBxmOmHuivG6Hb/QPc6HjHfCJ6f36gnlWc9a9TsC8Vddle+6PsS6+quMMTuj+bj7A==} + /@urql/core@4.1.4(graphql@16.6.0): + resolution: {integrity: sha512-wFm67yljv4uFAWNtPwcS1NMhF/n+p/68i+kZU6R1dPxhfq2nBW0142p4szeZsBDrtO7pBdOhp7YeSZROFFlXZg==} dependencies: '@0no-co/graphql.web': 1.0.4(graphql@16.6.0) wonka: 6.3.4 @@ -11544,14 +11562,13 @@ packages: - graphql dev: false - /@urql/core@4.1.3(graphql@16.8.0): - resolution: {integrity: sha512-Wapa58olpEJtZzSEuZNDxzBxmOmHuivG6Hb/QPc6HjHfCJ6f36gnlWc9a9TsC8Vddle+6PsS6+quMMTuj+bj7A==} + /@urql/core@4.1.4(graphql@16.8.0): + resolution: {integrity: sha512-wFm67yljv4uFAWNtPwcS1NMhF/n+p/68i+kZU6R1dPxhfq2nBW0142p4szeZsBDrtO7pBdOhp7YeSZROFFlXZg==} dependencies: '@0no-co/graphql.web': 1.0.4(graphql@16.8.0) wonka: 6.3.4 transitivePeerDependencies: - graphql - dev: false /@urql/core@4.1.4(graphql@16.8.1): resolution: {integrity: sha512-wFm67yljv4uFAWNtPwcS1NMhF/n+p/68i+kZU6R1dPxhfq2nBW0142p4szeZsBDrtO7pBdOhp7YeSZROFFlXZg==} @@ -11560,7 +11577,6 @@ packages: wonka: 6.3.4 transitivePeerDependencies: - graphql - dev: true /@urql/devtools@2.0.3(@urql/core@4.1.1)(graphql@16.8.0): resolution: {integrity: sha512-TktPLiBS9LcBPHD6qcnb8wqOVcg3Bx0iCtvQ80uPpfofwwBGJmqnQTjUdEFU6kwaLOFZULQ9+Uo4831G823mQw==} @@ -11576,7 +11592,7 @@ packages: /@urql/exchange-auth@2.1.6(graphql@16.6.0): resolution: {integrity: sha512-snOlt7p5kYq0KnPDuXkKe2qW3/BucQZOElvTeo3svLQuk9JiNJVnm6ffQ6QGiGO+G3AtMrctnno1+X44fLtDuQ==} dependencies: - '@urql/core': 4.1.3(graphql@16.6.0) + '@urql/core': 4.1.4(graphql@16.6.0) wonka: 6.3.4 transitivePeerDependencies: - graphql @@ -11585,7 +11601,7 @@ packages: /@urql/exchange-auth@2.1.6(graphql@16.8.0): resolution: {integrity: sha512-snOlt7p5kYq0KnPDuXkKe2qW3/BucQZOElvTeo3svLQuk9JiNJVnm6ffQ6QGiGO+G3AtMrctnno1+X44fLtDuQ==} dependencies: - '@urql/core': 4.1.3(graphql@16.8.0) + '@urql/core': 4.1.4(graphql@16.8.0) wonka: 6.3.4 transitivePeerDependencies: - graphql @@ -11605,7 +11621,7 @@ packages: resolution: {integrity: sha512-ajBtuOkCkWgYJVk8MYqlhTF2vNojEREitcUE62q8tUxC6zDHZybk8DUPe6RM0HUyUw6IPAnzmDf6djK5JOEvvw==} dependencies: '@0no-co/graphql.web': 1.0.4(graphql@16.8.0) - '@urql/core': 4.1.1(graphql@16.8.0) + '@urql/core': 4.1.4(graphql@16.8.0) wonka: 6.3.4 transitivePeerDependencies: - graphql @@ -11639,7 +11655,7 @@ packages: peerDependencies: vue: ^2.7.0 || ^3.0.0 dependencies: - '@urql/core': 4.1.3(graphql@16.6.0) + '@urql/core': 4.1.4(graphql@16.6.0) vue: 3.2.45 wonka: 6.3.4 transitivePeerDependencies: @@ -23741,7 +23757,7 @@ packages: graphql: 16.8.0 iterall: 1.3.0 symbol-observable: 1.2.0 - ws: 7.4.6 + ws: 7.5.9 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -28159,3 +28175,11 @@ packages: dependencies: '@tauri-apps/api': 1.5.1 dev: false + + github.com/tauri-apps/tauri-plugin-websocket/b55e90e968b9020d1eb8de97c97655bfa002dfd8: + resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-websocket/tar.gz/b55e90e968b9020d1eb8de97c97655bfa002dfd8} + name: tauri-plugin-websocket-api + version: 0.0.0 + dependencies: + '@tauri-apps/api': 1.5.1 + dev: false From 189fae236d9d1ab598baee53b08562213d1a9c69 Mon Sep 17 00:00:00 2001 From: Rokibul Islam Date: Fri, 17 Nov 2023 03:27:20 +0530 Subject: [PATCH 4/5] fix(backend): remove test cookie from callback --- packages/hoppscotch-backend/src/auth/helper.ts | 6 ------ packages/hoppscotch-common/src/components.d.ts | 16 +++++++++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/hoppscotch-backend/src/auth/helper.ts b/packages/hoppscotch-backend/src/auth/helper.ts index 4ffd84990c..4f2e541f13 100644 --- a/packages/hoppscotch-backend/src/auth/helper.ts +++ b/packages/hoppscotch-backend/src/auth/helper.ts @@ -90,12 +90,6 @@ export const authDesktopDeepLinkHandler = ( const desktopDeepLinkUrl = process.env.DESKTOP_DEEP_LINK_URL; return res .status(HttpStatus.OK) - .cookie('test_cookie', 'test_cookie', { - httpOnly: true, - secure: true, - sameSite: 'lax', - maxAge: 1000, - }) .redirect( `${desktopDeepLinkUrl}?access_token=${authTokens.access_token}&refresh_token=${authTokens.refresh_token}`, ); diff --git a/packages/hoppscotch-common/src/components.d.ts b/packages/hoppscotch-common/src/components.d.ts index 5adf4db2c9..9f9cc70f26 100644 --- a/packages/hoppscotch-common/src/components.d.ts +++ b/packages/hoppscotch-common/src/components.d.ts @@ -1,11 +1,11 @@ -/* eslint-disable */ -/* prettier-ignore */ -// @ts-nocheck -// Generated by unplugin-vue-components +// generated by unplugin-vue-components +// We suggest you to commit this file into source control // Read more: https://github.com/vuejs/core/pull/3399 +import '@vue/runtime-core' + export {} -declare module 'vue' { +declare module '@vue/runtime-core' { export interface GlobalComponents { AppActionHandler: typeof import('./components/app/ActionHandler.vue')['default'] AppAnnouncement: typeof import('./components/app/Announcement.vue')['default'] @@ -92,11 +92,13 @@ declare module 'vue' { HoppButtonPrimary: typeof import('@hoppscotch/ui')['HoppButtonPrimary'] HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary'] HoppSmartAnchor: typeof import('@hoppscotch/ui')['HoppSmartAnchor'] + HoppSmartAutoComplete: typeof import('@hoppscotch/ui')['HoppSmartAutoComplete'] HoppSmartCheckbox: typeof import('@hoppscotch/ui')['HoppSmartCheckbox'] HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal'] HoppSmartExpand: typeof import('@hoppscotch/ui')['HoppSmartExpand'] HoppSmartFileChip: typeof import('@hoppscotch/ui')['HoppSmartFileChip'] HoppSmartInput: typeof import('@hoppscotch/ui')['HoppSmartInput'] + HoppSmartIntersection: typeof import('@hoppscotch/ui')['HoppSmartIntersection'] HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem'] HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink'] HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal'] @@ -143,6 +145,7 @@ declare module 'vue' { IconLucideAlertTriangle: typeof import('~icons/lucide/alert-triangle')['default'] IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default'] IconLucideArrowUpRight: typeof import('~icons/lucide/arrow-up-right')['default'] + IconLucideBrush: typeof import('~icons/lucide/brush')['default'] IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default'] IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default'] IconLucideGlobe: typeof import('~icons/lucide/globe')['default'] @@ -152,8 +155,10 @@ declare module 'vue' { IconLucideLayers: typeof import('~icons/lucide/layers')['default'] IconLucideListEnd: typeof import('~icons/lucide/list-end')['default'] IconLucideMinus: typeof import('~icons/lucide/minus')['default'] + IconLucideRss: typeof import('~icons/lucide/rss')['default'] IconLucideSearch: typeof import('~icons/lucide/search')['default'] IconLucideUsers: typeof import('~icons/lucide/users')['default'] + IconLucideVerified: typeof import('~icons/lucide/verified')['default'] InterceptorsErrorPlaceholder: typeof import('./components/interceptors/ErrorPlaceholder.vue')['default'] InterceptorsExtensionSubtitle: typeof import('./components/interceptors/ExtensionSubtitle.vue')['default'] LensesHeadersRenderer: typeof import('./components/lenses/HeadersRenderer.vue')['default'] @@ -219,4 +224,5 @@ declare module 'vue' { WorkspaceCurrent: typeof import('./components/workspace/Current.vue')['default'] WorkspaceSelector: typeof import('./components/workspace/Selector.vue')['default'] } + } From 9b2099448b8c91173709396ef5be54045e0fb388 Mon Sep 17 00:00:00 2001 From: Rokibul Islam <14818143+rokibulislaam@users.noreply.github.com> Date: Fri, 17 Nov 2023 03:29:17 +0530 Subject: [PATCH 5/5] Delete packages/hoppscotch-common/src/components.d.ts --- .../hoppscotch-common/src/components.d.ts | 228 ------------------ 1 file changed, 228 deletions(-) delete mode 100644 packages/hoppscotch-common/src/components.d.ts diff --git a/packages/hoppscotch-common/src/components.d.ts b/packages/hoppscotch-common/src/components.d.ts deleted file mode 100644 index 9f9cc70f26..0000000000 --- a/packages/hoppscotch-common/src/components.d.ts +++ /dev/null @@ -1,228 +0,0 @@ -// generated by unplugin-vue-components -// We suggest you to commit this file into source control -// Read more: https://github.com/vuejs/core/pull/3399 -import '@vue/runtime-core' - -export {} - -declare module '@vue/runtime-core' { - export interface GlobalComponents { - AppActionHandler: typeof import('./components/app/ActionHandler.vue')['default'] - AppAnnouncement: typeof import('./components/app/Announcement.vue')['default'] - AppContextMenu: typeof import('./components/app/ContextMenu.vue')['default'] - AppDeveloperOptions: typeof import('./components/app/DeveloperOptions.vue')['default'] - AppFooter: typeof import('./components/app/Footer.vue')['default'] - AppGitHubStarButton: typeof import('./components/app/GitHubStarButton.vue')['default'] - AppHeader: typeof import('./components/app/Header.vue')['default'] - AppInspection: typeof import('./components/app/Inspection.vue')['default'] - AppInterceptor: typeof import('./components/app/Interceptor.vue')['default'] - AppLogo: typeof import('./components/app/Logo.vue')['default'] - AppOptions: typeof import('./components/app/Options.vue')['default'] - AppPaneLayout: typeof import('./components/app/PaneLayout.vue')['default'] - AppShare: typeof import('./components/app/Share.vue')['default'] - AppShortcuts: typeof import('./components/app/Shortcuts.vue')['default'] - AppShortcutsEntry: typeof import('./components/app/ShortcutsEntry.vue')['default'] - AppShortcutsPrompt: typeof import('./components/app/ShortcutsPrompt.vue')['default'] - AppSidenav: typeof import('./components/app/Sidenav.vue')['default'] - AppSpotlight: typeof import('./components/app/spotlight/index.vue')['default'] - AppSpotlightEntry: typeof import('./components/app/spotlight/Entry.vue')['default'] - AppSpotlightEntryGQLHistory: typeof import('./components/app/spotlight/entry/GQLHistory.vue')['default'] - AppSpotlightEntryGQLRequest: typeof import('./components/app/spotlight/entry/GQLRequest.vue')['default'] - AppSpotlightEntryIconSelected: typeof import('./components/app/spotlight/entry/IconSelected.vue')['default'] - AppSpotlightEntryRESTHistory: typeof import('./components/app/spotlight/entry/RESTHistory.vue')['default'] - AppSpotlightEntryRESTRequest: typeof import('./components/app/spotlight/entry/RESTRequest.vue')['default'] - AppSupport: typeof import('./components/app/Support.vue')['default'] - ButtonPrimary: typeof import('./../../hoppscotch-ui/src/components/button/Primary.vue')['default'] - ButtonSecondary: typeof import('./../../hoppscotch-ui/src/components/button/Secondary.vue')['default'] - Collections: typeof import('./components/collections/index.vue')['default'] - CollectionsAdd: typeof import('./components/collections/Add.vue')['default'] - CollectionsAddFolder: typeof import('./components/collections/AddFolder.vue')['default'] - CollectionsAddRequest: typeof import('./components/collections/AddRequest.vue')['default'] - CollectionsCollection: typeof import('./components/collections/Collection.vue')['default'] - CollectionsEdit: typeof import('./components/collections/Edit.vue')['default'] - CollectionsEditFolder: typeof import('./components/collections/EditFolder.vue')['default'] - CollectionsEditRequest: typeof import('./components/collections/EditRequest.vue')['default'] - CollectionsGraphql: typeof import('./components/collections/graphql/index.vue')['default'] - CollectionsGraphqlAdd: typeof import('./components/collections/graphql/Add.vue')['default'] - CollectionsGraphqlAddFolder: typeof import('./components/collections/graphql/AddFolder.vue')['default'] - CollectionsGraphqlAddRequest: typeof import('./components/collections/graphql/AddRequest.vue')['default'] - CollectionsGraphqlCollection: typeof import('./components/collections/graphql/Collection.vue')['default'] - CollectionsGraphqlEdit: typeof import('./components/collections/graphql/Edit.vue')['default'] - CollectionsGraphqlEditFolder: typeof import('./components/collections/graphql/EditFolder.vue')['default'] - CollectionsGraphqlEditRequest: typeof import('./components/collections/graphql/EditRequest.vue')['default'] - CollectionsGraphqlFolder: typeof import('./components/collections/graphql/Folder.vue')['default'] - CollectionsGraphqlImportExport: typeof import('./components/collections/graphql/ImportExport.vue')['default'] - CollectionsGraphqlRequest: typeof import('./components/collections/graphql/Request.vue')['default'] - CollectionsImportExport: typeof import('./components/collections/ImportExport.vue')['default'] - CollectionsMyCollections: typeof import('./components/collections/MyCollections.vue')['default'] - CollectionsRequest: typeof import('./components/collections/Request.vue')['default'] - CollectionsSaveRequest: typeof import('./components/collections/SaveRequest.vue')['default'] - CollectionsTeamCollections: typeof import('./components/collections/TeamCollections.vue')['default'] - CookiesAllModal: typeof import('./components/cookies/AllModal.vue')['default'] - CookiesEditCookie: typeof import('./components/cookies/EditCookie.vue')['default'] - Environments: typeof import('./components/environments/index.vue')['default'] - EnvironmentsAdd: typeof import('./components/environments/Add.vue')['default'] - EnvironmentsImportExport: typeof import('./components/environments/ImportExport.vue')['default'] - EnvironmentsMy: typeof import('./components/environments/my/index.vue')['default'] - EnvironmentsMyDetails: typeof import('./components/environments/my/Details.vue')['default'] - EnvironmentsMyEnvironment: typeof import('./components/environments/my/Environment.vue')['default'] - EnvironmentsSelector: typeof import('./components/environments/Selector.vue')['default'] - EnvironmentsTeams: typeof import('./components/environments/teams/index.vue')['default'] - EnvironmentsTeamsDetails: typeof import('./components/environments/teams/Details.vue')['default'] - EnvironmentsTeamsEnvironment: typeof import('./components/environments/teams/Environment.vue')['default'] - FirebaseLogin: typeof import('./components/firebase/Login.vue')['default'] - FirebaseLogout: typeof import('./components/firebase/Logout.vue')['default'] - GraphqlAuthorization: typeof import('./components/graphql/Authorization.vue')['default'] - GraphqlField: typeof import('./components/graphql/Field.vue')['default'] - GraphqlHeaders: typeof import('./components/graphql/Headers.vue')['default'] - GraphqlQuery: typeof import('./components/graphql/Query.vue')['default'] - GraphqlRequest: typeof import('./components/graphql/Request.vue')['default'] - GraphqlRequestOptions: typeof import('./components/graphql/RequestOptions.vue')['default'] - GraphqlRequestTab: typeof import('./components/graphql/RequestTab.vue')['default'] - GraphqlResponse: typeof import('./components/graphql/Response.vue')['default'] - GraphqlSidebar: typeof import('./components/graphql/Sidebar.vue')['default'] - GraphqlSubscriptionLog: typeof import('./components/graphql/SubscriptionLog.vue')['default'] - GraphqlTabHead: typeof import('./components/graphql/TabHead.vue')['default'] - GraphqlType: typeof import('./components/graphql/Type.vue')['default'] - GraphqlTypeLink: typeof import('./components/graphql/TypeLink.vue')['default'] - GraphqlVariable: typeof import('./components/graphql/Variable.vue')['default'] - History: typeof import('./components/history/index.vue')['default'] - HistoryGraphqlCard: typeof import('./components/history/graphql/Card.vue')['default'] - HistoryRestCard: typeof import('./components/history/rest/Card.vue')['default'] - HoppButtonPrimary: typeof import('@hoppscotch/ui')['HoppButtonPrimary'] - HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary'] - HoppSmartAnchor: typeof import('@hoppscotch/ui')['HoppSmartAnchor'] - HoppSmartAutoComplete: typeof import('@hoppscotch/ui')['HoppSmartAutoComplete'] - HoppSmartCheckbox: typeof import('@hoppscotch/ui')['HoppSmartCheckbox'] - HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal'] - HoppSmartExpand: typeof import('@hoppscotch/ui')['HoppSmartExpand'] - HoppSmartFileChip: typeof import('@hoppscotch/ui')['HoppSmartFileChip'] - HoppSmartInput: typeof import('@hoppscotch/ui')['HoppSmartInput'] - HoppSmartIntersection: typeof import('@hoppscotch/ui')['HoppSmartIntersection'] - HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem'] - HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink'] - HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal'] - HoppSmartPicture: typeof import('@hoppscotch/ui')['HoppSmartPicture'] - HoppSmartPlaceholder: typeof import('@hoppscotch/ui')['HoppSmartPlaceholder'] - HoppSmartProgressRing: typeof import('@hoppscotch/ui')['HoppSmartProgressRing'] - HoppSmartRadio: typeof import('@hoppscotch/ui')['HoppSmartRadio'] - HoppSmartRadioGroup: typeof import('@hoppscotch/ui')['HoppSmartRadioGroup'] - HoppSmartSlideOver: typeof import('@hoppscotch/ui')['HoppSmartSlideOver'] - HoppSmartSpinner: typeof import('@hoppscotch/ui')['HoppSmartSpinner'] - HoppSmartTab: typeof import('@hoppscotch/ui')['HoppSmartTab'] - HoppSmartTabs: typeof import('@hoppscotch/ui')['HoppSmartTabs'] - HoppSmartToggle: typeof import('@hoppscotch/ui')['HoppSmartToggle'] - HoppSmartTree: typeof import('@hoppscotch/ui')['HoppSmartTree'] - HoppSmartWindow: typeof import('@hoppscotch/ui')['HoppSmartWindow'] - HoppSmartWindows: typeof import('@hoppscotch/ui')['HoppSmartWindows'] - HttpAuthorization: typeof import('./components/http/Authorization.vue')['default'] - HttpAuthorizationApiKey: typeof import('./components/http/authorization/ApiKey.vue')['default'] - HttpAuthorizationBasic: typeof import('./components/http/authorization/Basic.vue')['default'] - HttpBody: typeof import('./components/http/Body.vue')['default'] - HttpBodyParameters: typeof import('./components/http/BodyParameters.vue')['default'] - HttpCodegenModal: typeof import('./components/http/CodegenModal.vue')['default'] - HttpHeaders: typeof import('./components/http/Headers.vue')['default'] - HttpImportCurl: typeof import('./components/http/ImportCurl.vue')['default'] - HttpOAuth2Authorization: typeof import('./components/http/OAuth2Authorization.vue')['default'] - HttpParameters: typeof import('./components/http/Parameters.vue')['default'] - HttpPreRequestScript: typeof import('./components/http/PreRequestScript.vue')['default'] - HttpRawBody: typeof import('./components/http/RawBody.vue')['default'] - HttpReqChangeConfirmModal: typeof import('./components/http/ReqChangeConfirmModal.vue')['default'] - HttpRequest: typeof import('./components/http/Request.vue')['default'] - HttpRequestOptions: typeof import('./components/http/RequestOptions.vue')['default'] - HttpRequestTab: typeof import('./components/http/RequestTab.vue')['default'] - HttpResponse: typeof import('./components/http/Response.vue')['default'] - HttpResponseMeta: typeof import('./components/http/ResponseMeta.vue')['default'] - HttpSidebar: typeof import('./components/http/Sidebar.vue')['default'] - HttpTabHead: typeof import('./components/http/TabHead.vue')['default'] - HttpTestResult: typeof import('./components/http/TestResult.vue')['default'] - HttpTestResultEntry: typeof import('./components/http/TestResultEntry.vue')['default'] - HttpTestResultEnv: typeof import('./components/http/TestResultEnv.vue')['default'] - HttpTestResultReport: typeof import('./components/http/TestResultReport.vue')['default'] - HttpTests: typeof import('./components/http/Tests.vue')['default'] - HttpURLEncodedParams: typeof import('./components/http/URLEncodedParams.vue')['default'] - IconLucideActivity: typeof import('~icons/lucide/activity')['default'] - IconLucideAlertTriangle: typeof import('~icons/lucide/alert-triangle')['default'] - IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default'] - IconLucideArrowUpRight: typeof import('~icons/lucide/arrow-up-right')['default'] - IconLucideBrush: typeof import('~icons/lucide/brush')['default'] - IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default'] - IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default'] - IconLucideGlobe: typeof import('~icons/lucide/globe')['default'] - IconLucideHelpCircle: typeof import('~icons/lucide/help-circle')['default'] - IconLucideInbox: typeof import('~icons/lucide/inbox')['default'] - IconLucideInfo: typeof import('~icons/lucide/info')['default'] - IconLucideLayers: typeof import('~icons/lucide/layers')['default'] - IconLucideListEnd: typeof import('~icons/lucide/list-end')['default'] - IconLucideMinus: typeof import('~icons/lucide/minus')['default'] - IconLucideRss: typeof import('~icons/lucide/rss')['default'] - IconLucideSearch: typeof import('~icons/lucide/search')['default'] - IconLucideUsers: typeof import('~icons/lucide/users')['default'] - IconLucideVerified: typeof import('~icons/lucide/verified')['default'] - InterceptorsErrorPlaceholder: typeof import('./components/interceptors/ErrorPlaceholder.vue')['default'] - InterceptorsExtensionSubtitle: typeof import('./components/interceptors/ExtensionSubtitle.vue')['default'] - LensesHeadersRenderer: typeof import('./components/lenses/HeadersRenderer.vue')['default'] - LensesHeadersRendererEntry: typeof import('./components/lenses/HeadersRendererEntry.vue')['default'] - LensesRenderersAudioLensRenderer: typeof import('./components/lenses/renderers/AudioLensRenderer.vue')['default'] - LensesRenderersHTMLLensRenderer: typeof import('./components/lenses/renderers/HTMLLensRenderer.vue')['default'] - LensesRenderersImageLensRenderer: typeof import('./components/lenses/renderers/ImageLensRenderer.vue')['default'] - LensesRenderersJSONLensRenderer: typeof import('./components/lenses/renderers/JSONLensRenderer.vue')['default'] - LensesRenderersPDFLensRenderer: typeof import('./components/lenses/renderers/PDFLensRenderer.vue')['default'] - LensesRenderersRawLensRenderer: typeof import('./components/lenses/renderers/RawLensRenderer.vue')['default'] - LensesRenderersVideoLensRenderer: typeof import('./components/lenses/renderers/VideoLensRenderer.vue')['default'] - LensesRenderersXMLLensRenderer: typeof import('./components/lenses/renderers/XMLLensRenderer.vue')['default'] - LensesResponseBodyRenderer: typeof import('./components/lenses/ResponseBodyRenderer.vue')['default'] - ProfileShortcode: typeof import('./components/profile/Shortcode.vue')['default'] - ProfileShortcodes: typeof import('./components/profile/Shortcodes.vue')['default'] - ProfileUserDelete: typeof import('./components/profile/UserDelete.vue')['default'] - RealtimeCommunication: typeof import('./components/realtime/Communication.vue')['default'] - RealtimeConnectionConfig: typeof import('./components/realtime/ConnectionConfig.vue')['default'] - RealtimeLog: typeof import('./components/realtime/Log.vue')['default'] - RealtimeLogEntry: typeof import('./components/realtime/LogEntry.vue')['default'] - RealtimeSubscription: typeof import('./components/realtime/Subscription.vue')['default'] - SettingsExtension: typeof import('./components/settings/Extension.vue')['default'] - SettingsProxy: typeof import('./components/settings/Proxy.vue')['default'] - SmartAccentModePicker: typeof import('./components/smart/AccentModePicker.vue')['default'] - SmartAnchor: typeof import('./../../hoppscotch-ui/src/components/smart/Anchor.vue')['default'] - SmartAutoComplete: typeof import('./../../hoppscotch-ui/src/components/smart/AutoComplete.vue')['default'] - SmartChangeLanguage: typeof import('./components/smart/ChangeLanguage.vue')['default'] - SmartCheckbox: typeof import('./../../hoppscotch-ui/src/components/smart/Checkbox.vue')['default'] - SmartColorModePicker: typeof import('./components/smart/ColorModePicker.vue')['default'] - SmartConfirmModal: typeof import('./../../hoppscotch-ui/src/components/smart/ConfirmModal.vue')['default'] - SmartEnvInput: typeof import('./components/smart/EnvInput.vue')['default'] - SmartExpand: typeof import('./../../hoppscotch-ui/src/components/smart/Expand.vue')['default'] - SmartFileChip: typeof import('./../../hoppscotch-ui/src/components/smart/FileChip.vue')['default'] - SmartInput: typeof import('./../../hoppscotch-ui/src/components/smart/Input.vue')['default'] - SmartIntersection: typeof import('./../../hoppscotch-ui/src/components/smart/Intersection.vue')['default'] - SmartItem: typeof import('./../../hoppscotch-ui/src/components/smart/Item.vue')['default'] - SmartLink: typeof import('./../../hoppscotch-ui/src/components/smart/Link.vue')['default'] - SmartModal: typeof import('./../../hoppscotch-ui/src/components/smart/Modal.vue')['default'] - SmartPicture: typeof import('./../../hoppscotch-ui/src/components/smart/Picture.vue')['default'] - SmartPlaceholder: typeof import('./../../hoppscotch-ui/src/components/smart/Placeholder.vue')['default'] - SmartProgressRing: typeof import('./../../hoppscotch-ui/src/components/smart/ProgressRing.vue')['default'] - SmartRadio: typeof import('./../../hoppscotch-ui/src/components/smart/Radio.vue')['default'] - SmartRadioGroup: typeof import('./../../hoppscotch-ui/src/components/smart/RadioGroup.vue')['default'] - SmartSlideOver: typeof import('./../../hoppscotch-ui/src/components/smart/SlideOver.vue')['default'] - SmartSpinner: typeof import('./../../hoppscotch-ui/src/components/smart/Spinner.vue')['default'] - SmartTab: typeof import('./../../hoppscotch-ui/src/components/smart/Tab.vue')['default'] - SmartTabs: typeof import('./../../hoppscotch-ui/src/components/smart/Tabs.vue')['default'] - SmartToggle: typeof import('./../../hoppscotch-ui/src/components/smart/Toggle.vue')['default'] - SmartTree: typeof import('./../../hoppscotch-ui/src/components/smart/Tree.vue')['default'] - SmartTreeBranch: typeof import('./../../hoppscotch-ui/src/components/smart/TreeBranch.vue')['default'] - SmartWindow: typeof import('./../../hoppscotch-ui/src/components/smart/Window.vue')['default'] - SmartWindows: typeof import('./../../hoppscotch-ui/src/components/smart/Windows.vue')['default'] - TabPrimary: typeof import('./components/tab/Primary.vue')['default'] - TabSecondary: typeof import('./components/tab/Secondary.vue')['default'] - Teams: typeof import('./components/teams/index.vue')['default'] - TeamsAdd: typeof import('./components/teams/Add.vue')['default'] - TeamsEdit: typeof import('./components/teams/Edit.vue')['default'] - TeamsInvite: typeof import('./components/teams/Invite.vue')['default'] - TeamsMemberStack: typeof import('./components/teams/MemberStack.vue')['default'] - TeamsModal: typeof import('./components/teams/Modal.vue')['default'] - TeamsTeam: typeof import('./components/teams/Team.vue')['default'] - Tippy: typeof import('vue-tippy')['Tippy'] - WorkspaceCurrent: typeof import('./components/workspace/Current.vue')['default'] - WorkspaceSelector: typeof import('./components/workspace/Selector.vue')['default'] - } - -}