Compare commits
2 Commits
release/20
...
refactor/i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00588bcc0a | ||
|
|
e24d0ce605 |
@@ -257,6 +257,7 @@
|
||||
"error": {
|
||||
"browser_support_sse": "This browser doesn't seems to have Server Sent Events support.",
|
||||
"check_console_details": "Check console log for details.",
|
||||
"check_how_to_add_origin": "Check how you can add an origin",
|
||||
"curl_invalid_format": "cURL is not formatted properly",
|
||||
"danger_zone": "Danger zone",
|
||||
"delete_account": "Your account is currently an owner in these teams:",
|
||||
@@ -277,6 +278,7 @@
|
||||
"no_environments_to_export": "No environments to export. Please create an environment to get started.",
|
||||
"no_results_found": "No matches found",
|
||||
"page_not_found": "This page could not be found",
|
||||
"please_install_extension": "Please install the extension and add origin to the extension.",
|
||||
"proxy_error": "Proxy error",
|
||||
"script_fail": "Could not execute pre-request script",
|
||||
"something_went_wrong": "Something went wrong",
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
"workbox-window": "^7.0.0",
|
||||
"xml-formatter": "^3.5.0",
|
||||
"yargs-parser": "^21.1.1",
|
||||
"zod": "^3.22.2"
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
|
||||
|
||||
12
packages/hoppscotch-common/src/components.d.ts
vendored
12
packages/hoppscotch-common/src/components.d.ts
vendored
@@ -1,11 +1,11 @@
|
||||
// generated by unplugin-vue-components
|
||||
// We suggest you to commit this file into source control
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
import '@vue/runtime-core'
|
||||
|
||||
export {}
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
AppActionHandler: typeof import('./components/app/ActionHandler.vue')['default']
|
||||
AppAnnouncement: typeof import('./components/app/Announcement.vue')['default']
|
||||
@@ -154,6 +154,7 @@ declare module '@vue/runtime-core' {
|
||||
IconLucideMinus: typeof import('~icons/lucide/minus')['default']
|
||||
IconLucideSearch: typeof import('~icons/lucide/search')['default']
|
||||
IconLucideUsers: typeof import('~icons/lucide/users')['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']
|
||||
@@ -218,5 +219,4 @@ declare module '@vue/runtime-core' {
|
||||
WorkspaceCurrent: typeof import('./components/workspace/Current.vue')['default']
|
||||
WorkspaceSelector: typeof import('./components/workspace/Selector.vue')['default']
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -58,8 +58,8 @@
|
||||
v-for="(field, index) in filteredQueryFields"
|
||||
:key="`field-${index}`"
|
||||
:gql-field="field"
|
||||
@jump-to-type="handleJumpToType"
|
||||
class="p-4"
|
||||
@jump-to-type="handleJumpToType"
|
||||
/>
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
@@ -72,8 +72,8 @@
|
||||
v-for="(field, index) in filteredMutationFields"
|
||||
:key="`field-${index}`"
|
||||
:gql-field="field"
|
||||
@jump-to-type="handleJumpToType"
|
||||
class="p-4"
|
||||
@jump-to-type="handleJumpToType"
|
||||
/>
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
@@ -86,8 +86,8 @@
|
||||
v-for="(field, index) in filteredSubscriptionFields"
|
||||
:key="`field-${index}`"
|
||||
:gql-field="field"
|
||||
@jump-to-type="handleJumpToType"
|
||||
class="p-4"
|
||||
@jump-to-type="handleJumpToType"
|
||||
/>
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
|
||||
@@ -43,6 +43,7 @@ import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { tokenRequest } from "~/helpers/oauth"
|
||||
import { getCombinedEnvVariables } from "~/helpers/preRequest"
|
||||
import * as E from "fp-ts/Either"
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
@@ -98,7 +99,11 @@ const handleAccessTokenRequest = async () => {
|
||||
clientSecret: parseTemplateString(clientSecret.value, envVars),
|
||||
scope: parseTemplateString(scope.value, envVars),
|
||||
}
|
||||
await tokenRequest(tokenReqParams)
|
||||
const res = await tokenRequest(tokenReqParams)
|
||||
|
||||
if (res && E.isLeft(res)) {
|
||||
toast.error(res.left)
|
||||
}
|
||||
} catch (e) {
|
||||
toast.error(`${e}`)
|
||||
}
|
||||
|
||||
@@ -350,7 +350,6 @@ const newSendRequest = async () => {
|
||||
const streamResult = await streamPromise
|
||||
|
||||
requestCancelFunc.value = cancel
|
||||
|
||||
if (E.isRight(streamResult)) {
|
||||
subscribeToStream(
|
||||
streamResult.right,
|
||||
@@ -365,6 +364,20 @@ const newSendRequest = async () => {
|
||||
loading.value = false
|
||||
},
|
||||
() => {
|
||||
// TODO: Change this any to a proper type
|
||||
const result = (streamResult.right as any).value
|
||||
if (
|
||||
result.type === "network_fail" &&
|
||||
result.error?.error === "NO_PW_EXT_HOOK"
|
||||
) {
|
||||
const errorResponse: HoppRESTResponse = {
|
||||
type: "extension_error",
|
||||
error: result.error.humanMessage.heading,
|
||||
component: result.error.component,
|
||||
req: result.req,
|
||||
}
|
||||
updateRESTResponse(errorResponse)
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
)
|
||||
|
||||
@@ -11,6 +11,12 @@
|
||||
<HoppSmartSpinner class="my-4" />
|
||||
<span class="text-secondaryLight">{{ t("state.loading") }}</span>
|
||||
</div>
|
||||
|
||||
<component
|
||||
:is="response.component"
|
||||
v-if="response.type === 'extension_error'"
|
||||
class="flex-1"
|
||||
/>
|
||||
<HoppSmartPlaceholder
|
||||
v-if="response.type === 'network_fail'"
|
||||
:src="`/images/states/${colorMode.value}/youre_lost.svg`"
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<HoppSmartPlaceholder
|
||||
:src="`/images/states/${colorMode.value}/youre_lost.svg`"
|
||||
:alt="`${t('error.network_fail')}`"
|
||||
:heading="t('error.network_fail')"
|
||||
large
|
||||
>
|
||||
<div class="my-1 text-secondaryLight flex flex-col items-center">
|
||||
<span>
|
||||
{{ t("error.please_install_extension") }}
|
||||
</span>
|
||||
<span>
|
||||
{{ t("error.check_how_to_add_origin") }}
|
||||
<HoppSmartLink
|
||||
blank
|
||||
to="https://docs.hoppscotch.io/documentation/features/interceptor#browser-extension"
|
||||
class="text-accent hover:text-accentDark"
|
||||
>
|
||||
here
|
||||
</HoppSmartLink>
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex flex-col py-4 space-y-2">
|
||||
<span>
|
||||
<HoppSmartItem
|
||||
to="https://chrome.google.com/webstore/detail/hoppscotch-browser-extens/amknoiejhlmhancpahfcfcfhllgkpbld"
|
||||
blank
|
||||
:icon="IconChrome"
|
||||
label="Chrome"
|
||||
:info-icon="hasChromeExtInstalled ? IconCheckCircle : null"
|
||||
:active-info-icon="hasChromeExtInstalled"
|
||||
outline
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
<HoppSmartItem
|
||||
to="https://addons.mozilla.org/en-US/firefox/addon/hoppscotch"
|
||||
blank
|
||||
:icon="IconFirefox"
|
||||
label="Firefox"
|
||||
:info-icon="hasFirefoxExtInstalled ? IconCheckCircle : null"
|
||||
:active-info-icon="hasFirefoxExtInstalled"
|
||||
outline
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div class="py-4 space-y-4">
|
||||
<div class="flex items-center">
|
||||
<HoppSmartToggle
|
||||
:on="extensionEnabled"
|
||||
@change="extensionEnabled = !extensionEnabled"
|
||||
>
|
||||
{{ t("settings.extensions_use_toggle") }}
|
||||
</HoppSmartToggle>
|
||||
</div>
|
||||
</div>
|
||||
</HoppSmartPlaceholder>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import IconChrome from "~icons/brands/chrome"
|
||||
import IconFirefox from "~icons/brands/firefox"
|
||||
import IconCheckCircle from "~icons/lucide/check-circle"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { ExtensionInterceptorService } from "~/platform/std/interceptors/extension"
|
||||
import { useService } from "dioc/vue"
|
||||
import { computed } from "vue"
|
||||
import { InterceptorService } from "~/services/interceptor.service"
|
||||
import { platform } from "~/platform"
|
||||
import { useColorMode } from "~/composables/theming"
|
||||
|
||||
const colorMode = useColorMode()
|
||||
const t = useI18n()
|
||||
|
||||
const interceptorService = useService(InterceptorService)
|
||||
const extensionService = useService(ExtensionInterceptorService)
|
||||
|
||||
const hasChromeExtInstalled = extensionService.chromeExtensionInstalled
|
||||
const hasFirefoxExtInstalled = extensionService.firefoxExtensionInstalled
|
||||
|
||||
const extensionEnabled = computed({
|
||||
get() {
|
||||
return (
|
||||
interceptorService.currentInterceptorID.value ===
|
||||
extensionService.interceptorID
|
||||
)
|
||||
},
|
||||
set(active) {
|
||||
if (active) {
|
||||
interceptorService.currentInterceptorID.value =
|
||||
extensionService.interceptorID
|
||||
} else {
|
||||
interceptorService.currentInterceptorID.value =
|
||||
platform.interceptors.default
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
@@ -152,12 +152,14 @@ export function useStreamSubscriber(): {
|
||||
error?: (e: any) => void,
|
||||
complete?: () => void
|
||||
) => {
|
||||
const sub = stream.subscribe({
|
||||
let sub: Subscription | null = null
|
||||
|
||||
sub = stream.subscribe({
|
||||
next,
|
||||
error,
|
||||
complete: () => {
|
||||
if (complete) complete()
|
||||
subs.splice(subs.indexOf(sub), 1)
|
||||
if (sub) subs.splice(subs.indexOf(sub), 1)
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -4,7 +4,10 @@ import {
|
||||
removeLocalConfig,
|
||||
} from "~/newstore/localpersistence"
|
||||
|
||||
const redirectUri = `${window.location.origin}/`
|
||||
import * as E from "fp-ts/Either"
|
||||
import { z } from "zod"
|
||||
|
||||
const redirectUri = `${window.location.origin}/oauth`
|
||||
|
||||
// GENERAL HELPER FUNCTIONS
|
||||
|
||||
@@ -16,7 +19,7 @@ const redirectUri = `${window.location.origin}/`
|
||||
* @returns {Object}
|
||||
*/
|
||||
|
||||
const sendPostRequest = async (url, params) => {
|
||||
const sendPostRequest = async (url: string, params: Record<string, string>) => {
|
||||
const body = Object.keys(params)
|
||||
.map((key) => `${key}=${params[key]}`)
|
||||
.join("&")
|
||||
@@ -30,9 +33,9 @@ const sendPostRequest = async (url, params) => {
|
||||
try {
|
||||
const response = await fetch(url, options)
|
||||
const data = await response.json()
|
||||
return data
|
||||
return E.right(data)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return E.left("AUTH_TOKEN_REQUEST_FAILED")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +46,7 @@ const sendPostRequest = async (url, params) => {
|
||||
* @returns {Object}
|
||||
*/
|
||||
|
||||
const parseQueryString = (searchQuery) => {
|
||||
const parseQueryString = (searchQuery: string): Record<string, string> => {
|
||||
if (searchQuery === "") {
|
||||
return {}
|
||||
}
|
||||
@@ -61,7 +64,7 @@ const parseQueryString = (searchQuery) => {
|
||||
* @returns {Object}
|
||||
*/
|
||||
|
||||
const getTokenConfiguration = async (endpoint) => {
|
||||
const getTokenConfiguration = async (endpoint: string) => {
|
||||
const options = {
|
||||
method: "GET",
|
||||
headers: {
|
||||
@@ -71,9 +74,9 @@ const getTokenConfiguration = async (endpoint) => {
|
||||
try {
|
||||
const response = await fetch(endpoint, options)
|
||||
const config = await response.json()
|
||||
return config
|
||||
return E.right(config)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return E.left("OIDC_DISCOVERY_FAILED")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +100,7 @@ const generateRandomString = () => {
|
||||
* @returns {Promise<ArrayBuffer>}
|
||||
*/
|
||||
|
||||
const sha256 = (plain) => {
|
||||
const sha256 = (plain: string) => {
|
||||
const encoder = new TextEncoder()
|
||||
const data = encoder.encode(plain)
|
||||
return window.crypto.subtle.digest("SHA-256", data)
|
||||
@@ -111,15 +114,18 @@ const sha256 = (plain) => {
|
||||
*/
|
||||
|
||||
const base64urlencode = (
|
||||
str // Converts the ArrayBuffer to string using Uint8 array to convert to what btoa accepts.
|
||||
) =>
|
||||
str: ArrayBuffer // Converts the ArrayBuffer to string using Uint8 array to convert to what btoa accepts.
|
||||
) => {
|
||||
const hashArray = Array.from(new Uint8Array(str))
|
||||
|
||||
// btoa accepts chars only within ascii 0-255 and base64 encodes them.
|
||||
// Then convert the base64 encoded to base64url encoded
|
||||
// (replace + with -, replace / with _, trim trailing =)
|
||||
btoa(String.fromCharCode.apply(null, new Uint8Array(str)))
|
||||
return btoa(String.fromCharCode.apply(null, hashArray))
|
||||
.replace(/\+/g, "-")
|
||||
.replace(/\//g, "_")
|
||||
.replace(/=+$/, "")
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the base64-urlencoded sha256 hash for the PKCE challenge
|
||||
@@ -128,13 +134,23 @@ const base64urlencode = (
|
||||
* @returns {String}
|
||||
*/
|
||||
|
||||
const pkceChallengeFromVerifier = async (v) => {
|
||||
const pkceChallengeFromVerifier = async (v: string) => {
|
||||
const hashed = await sha256(v)
|
||||
return base64urlencode(hashed)
|
||||
}
|
||||
|
||||
// OAUTH REQUEST
|
||||
|
||||
type TokenRequestParams = {
|
||||
oidcDiscoveryUrl: string
|
||||
grantType: string
|
||||
authUrl: string
|
||||
accessTokenUrl: string
|
||||
clientId: string
|
||||
clientSecret: string
|
||||
scope: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates PKCE Auth Code flow when requested
|
||||
*
|
||||
@@ -150,16 +166,28 @@ const tokenRequest = async ({
|
||||
clientId,
|
||||
clientSecret,
|
||||
scope,
|
||||
}) => {
|
||||
}: TokenRequestParams) => {
|
||||
// Check oauth configuration
|
||||
if (oidcDiscoveryUrl !== "") {
|
||||
// eslint-disable-next-line camelcase
|
||||
const { authorization_endpoint, token_endpoint } =
|
||||
await getTokenConfiguration(oidcDiscoveryUrl)
|
||||
// eslint-disable-next-line camelcase
|
||||
authUrl = authorization_endpoint
|
||||
// eslint-disable-next-line camelcase
|
||||
accessTokenUrl = token_endpoint
|
||||
const res = await getTokenConfiguration(oidcDiscoveryUrl)
|
||||
|
||||
const OIDCConfigurationSchema = z.object({
|
||||
authorization_endpoint: z.string(),
|
||||
token_endpoint: z.string(),
|
||||
})
|
||||
|
||||
if (E.isLeft(res)) {
|
||||
return E.left("OIDC_DISCOVERY_FAILED" as const)
|
||||
}
|
||||
|
||||
const parsedOIDCConfiguration = OIDCConfigurationSchema.safeParse(res.right)
|
||||
|
||||
if (!parsedOIDCConfiguration.success) {
|
||||
return E.left("OIDC_DISCOVERY_FAILED" as const)
|
||||
}
|
||||
|
||||
authUrl = parsedOIDCConfiguration.data.authorization_endpoint
|
||||
accessTokenUrl = parsedOIDCConfiguration.data.token_endpoint
|
||||
}
|
||||
// Store oauth information
|
||||
setLocalConfig("tokenEndpoint", accessTokenUrl)
|
||||
@@ -190,7 +218,7 @@ const tokenRequest = async ({
|
||||
)}&code_challenge_method=S256`
|
||||
|
||||
// Redirect to the authorization server
|
||||
window.location = buildUrl()
|
||||
window.location.assign(buildUrl())
|
||||
}
|
||||
|
||||
// OAUTH REDIRECT HANDLING
|
||||
@@ -202,44 +230,84 @@ const tokenRequest = async ({
|
||||
* @returns {Promise<any | void>}
|
||||
*/
|
||||
|
||||
const oauthRedirect = () => {
|
||||
let tokenResponse = ""
|
||||
const q = parseQueryString(window.location.search.substring(1))
|
||||
const handleOAuthRedirect = async () => {
|
||||
const queryParams = parseQueryString(window.location.search.substring(1))
|
||||
|
||||
// Check if the server returned an error string
|
||||
if (q.error) {
|
||||
alert(`Error returned from authorization server: ${q.error}`)
|
||||
if (queryParams.error) {
|
||||
return E.left("AUTH_SERVER_RETURNED_ERROR" as const)
|
||||
}
|
||||
|
||||
if (!queryParams.code) {
|
||||
return E.left("NO_AUTH_CODE" as const)
|
||||
}
|
||||
|
||||
// If the server returned an authorization code, attempt to exchange it for an access token
|
||||
if (q.code) {
|
||||
// Verify state matches what we set at the beginning
|
||||
if (getLocalConfig("pkce_state") !== q.state) {
|
||||
alert("Invalid state")
|
||||
Promise.reject(tokenResponse)
|
||||
} else {
|
||||
try {
|
||||
// Exchange the authorization code for an access token
|
||||
tokenResponse = sendPostRequest(getLocalConfig("tokenEndpoint"), {
|
||||
grant_type: "authorization_code",
|
||||
code: q.code,
|
||||
client_id: getLocalConfig("client_id"),
|
||||
client_secret: getLocalConfig("client_secret"),
|
||||
redirect_uri: redirectUri,
|
||||
code_verifier: getLocalConfig("pkce_codeVerifier"),
|
||||
})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return Promise.reject(tokenResponse)
|
||||
}
|
||||
}
|
||||
// Clean these up since we don't need them anymore
|
||||
removeLocalConfig("pkce_state")
|
||||
removeLocalConfig("pkce_codeVerifier")
|
||||
removeLocalConfig("tokenEndpoint")
|
||||
removeLocalConfig("client_id")
|
||||
removeLocalConfig("client_secret")
|
||||
return tokenResponse
|
||||
// Verify state matches what we set at the beginning
|
||||
if (getLocalConfig("pkce_state") !== queryParams.state) {
|
||||
return E.left("INVALID_STATE" as const)
|
||||
}
|
||||
return Promise.reject(tokenResponse)
|
||||
|
||||
const tokenEndpoint = getLocalConfig("tokenEndpoint")
|
||||
const clientID = getLocalConfig("client_id")
|
||||
const clientSecret = getLocalConfig("client_secret")
|
||||
const codeVerifier = getLocalConfig("pkce_codeVerifier")
|
||||
|
||||
if (!tokenEndpoint) {
|
||||
return E.left("NO_TOKEN_ENDPOINT" as const)
|
||||
}
|
||||
|
||||
if (!clientID) {
|
||||
return E.left("NO_CLIENT_ID" as const)
|
||||
}
|
||||
|
||||
if (!clientSecret) {
|
||||
return E.left("NO_CLIENT_SECRET" as const)
|
||||
}
|
||||
|
||||
if (!codeVerifier) {
|
||||
return E.left("NO_CODE_VERIFIER" as const)
|
||||
}
|
||||
|
||||
// Exchange the authorization code for an access token
|
||||
const tokenResponse: E.Either<string, any> = await sendPostRequest(
|
||||
tokenEndpoint,
|
||||
{
|
||||
grant_type: "authorization_code",
|
||||
code: queryParams.code,
|
||||
client_id: clientID,
|
||||
client_secret: clientSecret,
|
||||
redirect_uri: redirectUri,
|
||||
code_verifier: codeVerifier,
|
||||
}
|
||||
)
|
||||
|
||||
// Clean these up since we don't need them anymore
|
||||
clearPKCEState()
|
||||
|
||||
if (E.isLeft(tokenResponse)) {
|
||||
return E.left("AUTH_TOKEN_REQUEST_FAILED" as const)
|
||||
}
|
||||
|
||||
const withAccessTokenSchema = z.object({
|
||||
access_token: z.string(),
|
||||
})
|
||||
|
||||
const parsedTokenResponse = withAccessTokenSchema.safeParse(
|
||||
tokenResponse.right
|
||||
)
|
||||
|
||||
return parsedTokenResponse.success
|
||||
? E.right(parsedTokenResponse.data)
|
||||
: E.left("AUTH_TOKEN_REQUEST_INVALID_RESPONSE" as const)
|
||||
}
|
||||
|
||||
export { tokenRequest, oauthRedirect }
|
||||
const clearPKCEState = () => {
|
||||
removeLocalConfig("pkce_state")
|
||||
removeLocalConfig("pkce_codeVerifier")
|
||||
removeLocalConfig("tokenEndpoint")
|
||||
removeLocalConfig("client_id")
|
||||
removeLocalConfig("client_secret")
|
||||
}
|
||||
|
||||
export { tokenRequest, handleOAuthRedirect }
|
||||
@@ -1,4 +1,5 @@
|
||||
import { HoppRESTRequest } from "@hoppscotch/data"
|
||||
import { Component } from "vue"
|
||||
|
||||
export type HoppRESTResponseHeader = { key: string; value: string }
|
||||
|
||||
@@ -39,3 +40,9 @@ export type HoppRESTResponse =
|
||||
|
||||
req: HoppRESTRequest
|
||||
}
|
||||
| {
|
||||
type: "extension_error"
|
||||
error: string
|
||||
component: Component
|
||||
req: HoppRESTRequest
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, onBeforeUnmount, onBeforeMount } from "vue"
|
||||
import { ref, onMounted, onBeforeUnmount } from "vue"
|
||||
import { safelyExtractRESTRequest } from "@hoppscotch/data"
|
||||
import { translateExtURLParams } from "~/helpers/RESTExtURLParams"
|
||||
import { useRoute } from "vue-router"
|
||||
@@ -114,7 +114,6 @@ import {
|
||||
} from "rxjs"
|
||||
import { useToast } from "~/composables/toast"
|
||||
import { watchDebounced } from "@vueuse/core"
|
||||
import { oauthRedirect } from "~/helpers/oauth"
|
||||
import { useReadonlyStream } from "~/composables/stream"
|
||||
import {
|
||||
changeCurrentSyncStatus,
|
||||
@@ -414,28 +413,6 @@ function setupTabStateSync() {
|
||||
})
|
||||
}
|
||||
|
||||
function oAuthURL() {
|
||||
onBeforeMount(async () => {
|
||||
try {
|
||||
const tokenInfo = await oauthRedirect()
|
||||
if (
|
||||
typeof tokenInfo === "object" &&
|
||||
tokenInfo.hasOwnProperty("access_token")
|
||||
) {
|
||||
if (
|
||||
tabs.currentActiveTab.value.document.request.auth.authType ===
|
||||
"oauth-2"
|
||||
) {
|
||||
tabs.currentActiveTab.value.document.request.auth.token =
|
||||
tokenInfo.access_token
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch (_) {}
|
||||
})
|
||||
}
|
||||
|
||||
defineActionHandler("contextmenu.open", ({ position, text }) => {
|
||||
if (text) {
|
||||
contextMenu.value = {
|
||||
@@ -454,7 +431,6 @@ defineActionHandler("contextmenu.open", ({ position, text }) => {
|
||||
|
||||
setupTabStateSync()
|
||||
bindRequestToURLParams()
|
||||
oAuthURL()
|
||||
|
||||
defineActionHandler("rest.request.open", ({ doc }) => {
|
||||
tabs.createNewTab(doc)
|
||||
|
||||
43
packages/hoppscotch-common/src/pages/oauth.vue
Normal file
43
packages/hoppscotch-common/src/pages/oauth.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<div class="flex justify-center items-center">
|
||||
<HoppSmartSpinner />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { handleOAuthRedirect } from "~/helpers/oauth"
|
||||
import { useToast } from "~/composables/toast"
|
||||
|
||||
import * as E from "fp-ts/Either"
|
||||
import { useService } from "dioc/vue"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
import { onMounted } from "vue"
|
||||
|
||||
import { useRouter } from "vue-router"
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
const tabs = useService(RESTTabService)
|
||||
|
||||
onMounted(async () => {
|
||||
const tokenInfo = await handleOAuthRedirect()
|
||||
|
||||
if (E.isLeft(tokenInfo)) {
|
||||
toast.error(tokenInfo.left)
|
||||
router.push("/")
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
tabs.currentActiveTab.value.document.request.auth.authType === "oauth-2"
|
||||
) {
|
||||
tabs.currentActiveTab.value.document.request.auth.token =
|
||||
tokenInfo.right.access_token
|
||||
|
||||
router.push("/")
|
||||
return
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -12,6 +12,7 @@ import { computed, readonly, ref } from "vue"
|
||||
import { browserIsChrome, browserIsFirefox } from "~/helpers/utils/userAgent"
|
||||
import SettingsExtension from "~/components/settings/Extension.vue"
|
||||
import InterceptorsExtensionSubtitle from "~/components/interceptors/ExtensionSubtitle.vue"
|
||||
import InterceptorsErrorPlaceholder from "~/components/interceptors/ErrorPlaceholder.vue"
|
||||
|
||||
export const defineSubscribableObject = <T extends object>(obj: T) => {
|
||||
const proxyObject = {
|
||||
@@ -217,6 +218,7 @@ export class ExtensionInterceptorService
|
||||
description: () => "Heading not found",
|
||||
},
|
||||
error: "NO_PW_EXT_HOOK",
|
||||
component: InterceptorsErrorPlaceholder,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ export type InterceptorError =
|
||||
description: (t: ReturnType<typeof getI18n>) => string
|
||||
}
|
||||
error?: unknown
|
||||
component?: Component
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
22
pnpm-lock.yaml
generated
22
pnpm-lock.yaml
generated
@@ -611,8 +611,8 @@ importers:
|
||||
specifier: ^21.1.1
|
||||
version: 21.1.1
|
||||
zod:
|
||||
specifier: ^3.22.2
|
||||
version: 3.22.2
|
||||
specifier: ^3.22.4
|
||||
version: 3.22.4
|
||||
devDependencies:
|
||||
'@esbuild-plugins/node-globals-polyfill':
|
||||
specifier: ^0.2.3
|
||||
@@ -5531,9 +5531,9 @@ packages:
|
||||
'@parcel/watcher':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/generator': 7.22.10
|
||||
'@babel/template': 7.22.5
|
||||
'@babel/types': 7.22.10
|
||||
'@babel/generator': 7.23.0
|
||||
'@babel/template': 7.22.15
|
||||
'@babel/types': 7.23.0
|
||||
'@graphql-codegen/core': 4.0.0(graphql@16.8.0)
|
||||
'@graphql-codegen/plugin-helpers': 5.0.1(graphql@16.8.0)
|
||||
'@graphql-tools/apollo-engine-loader': 8.0.0(graphql@16.8.0)
|
||||
@@ -5545,7 +5545,7 @@ packages:
|
||||
'@graphql-tools/load': 8.0.0(graphql@16.8.0)
|
||||
'@graphql-tools/prisma-loader': 8.0.1(@types/node@17.0.27)(graphql@16.8.0)
|
||||
'@graphql-tools/url-loader': 8.0.0(@types/node@17.0.27)(graphql@16.8.0)
|
||||
'@graphql-tools/utils': 10.0.5(graphql@16.8.0)
|
||||
'@graphql-tools/utils': 10.0.6(graphql@16.8.0)
|
||||
'@whatwg-node/fetch': 0.8.8
|
||||
chalk: 4.1.2
|
||||
cosmiconfig: 8.2.0
|
||||
@@ -7390,7 +7390,7 @@ packages:
|
||||
graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
|
||||
dependencies:
|
||||
'@graphql-tools/url-loader': 8.0.0(@types/node@17.0.27)(graphql@16.8.0)
|
||||
'@graphql-tools/utils': 10.0.5(graphql@16.8.0)
|
||||
'@graphql-tools/utils': 10.0.6(graphql@16.8.0)
|
||||
'@types/js-yaml': 4.0.5
|
||||
'@types/json-stable-stringify': 1.0.34
|
||||
'@whatwg-node/fetch': 0.9.9
|
||||
@@ -7672,10 +7672,10 @@ packages:
|
||||
'@types/ws': 8.5.5
|
||||
'@whatwg-node/fetch': 0.8.8
|
||||
graphql: 16.6.0
|
||||
isomorphic-ws: 5.0.0(ws@8.13.0)
|
||||
isomorphic-ws: 5.0.0(ws@8.14.2)
|
||||
tslib: 2.6.2
|
||||
value-or-promise: 1.0.12
|
||||
ws: 8.13.0
|
||||
ws: 8.14.2
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- bufferutil
|
||||
@@ -23744,7 +23744,7 @@ packages:
|
||||
graphql: 16.8.1
|
||||
iterall: 1.3.0
|
||||
symbol-observable: 1.2.0
|
||||
ws: 7.4.6
|
||||
ws: 7.5.9
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- utf-8-validate
|
||||
@@ -27760,7 +27760,6 @@ packages:
|
||||
optional: true
|
||||
utf-8-validate:
|
||||
optional: true
|
||||
dev: true
|
||||
|
||||
/ws@8.12.1:
|
||||
resolution: {integrity: sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew==}
|
||||
@@ -28069,6 +28068,7 @@ packages:
|
||||
|
||||
/zod@3.22.2:
|
||||
resolution: {integrity: sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg==}
|
||||
dev: true
|
||||
|
||||
/zod@3.22.4:
|
||||
resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==}
|
||||
|
||||
Reference in New Issue
Block a user