11<template >
22 <div class =" space-y-4" >
3- <div >
3+ <div v-if =" showCorsError" >
4+ <div class =" flex items-start space-x-4" >
5+ <icon-lucide-alert-triangle
6+ class =" text-yellow-500 flex-shrink-0 mt-1"
7+ />
8+ <div >
9+ <p class =" text-secondaryDark" >
10+ {{ t("import.cors_error_modal.description") }}
11+ </p >
12+ <p class =" text-secondaryLight mt-2" >
13+ {{ t("import.cors_error_modal.explanation") }}
14+ </p >
15+ </div >
16+ </div >
17+ </div >
18+ <div v-else >
419 <p class =" flex items-center" >
520 <span
621 class =" inline-flex items-center justify-center flex-shrink-0 mr-4 border-4 rounded-full border-primary text-dividerDark"
3348 <div >
3449 <HoppButtonPrimary
3550 class =" w-full"
36- :label =" t('import.title')"
51+ :label ="
52+ showCorsError
53+ ? t('import.cors_error_modal.retry_with_proxy')
54+ : t('import.title')
55+ "
3756 :disabled =" disableImportCTA"
3857 :loading =" isFetchingUrl || loading"
39- @click =" fetchUrlData"
58+ @click =" showCorsError ? retryWithProxy() : fetchUrlData() "
4059 />
4160 </div >
4261 </div >
4362</template >
4463
4564<script setup lang="ts">
46- import { computed , ref , watch } from " vue"
4765import { useI18n } from " @composables/i18n"
48- import { useToast } from " ~/composables/toast"
49- import { KernelInterceptorService } from " ~/services/kernel-interceptor.service"
5066import { useService } from " dioc/vue"
5167import * as E from " fp-ts/Either"
5268import * as O from " fp-ts/Option"
69+ import { computed , ref , watch } from " vue"
70+ import { useToast } from " ~/composables/toast"
5371import { parseBodyAsJSONOrYAML } from " ~/helpers/functional/json"
72+ import { ProxyKernelInterceptorService } from " ~/platform/std/kernel-interceptors/proxy"
73+ import { KernelInterceptorService } from " ~/services/kernel-interceptor.service"
5474
5575const interceptorService = useService (KernelInterceptorService )
76+ const proxyInterceptorService = useService (ProxyKernelInterceptorService )
5677
5778const t = useI18n ()
58-
5979const toast = useToast ()
6080
6181const props = withDefaults (
@@ -74,53 +94,103 @@ const emit = defineEmits<{
7494
7595const inputChooseGistToImportFrom = ref <string >(" " )
7696const hasURL = ref (false )
77-
7897const isFetchingUrl = ref (false )
98+ const showCorsError = ref (false )
7999
80100watch (inputChooseGistToImportFrom , (url ) => {
81101 hasURL .value = !! url
82102})
83103
84104const disableImportCTA = computed (() => ! hasURL .value || props .loading )
85105
106+ const isCorsError = (error : any ): boolean => {
107+ // Check for common CORS error patterns
108+ return (
109+ error ?.kind === " network" ||
110+ error ?.message ?.includes (" CORS" ) ||
111+ error ?.message ?.includes (" Access to fetch" ) ||
112+ error ?.message ?.includes (" Cross-Origin" ) ||
113+ error ?.code === " ERR_NETWORK" ||
114+ error ?.name === " TypeError"
115+ )
116+ }
117+
86118const urlFetchLogic =
87119 props .fetchLogic ??
88120 async function (url : string ) {
89- const { response } = interceptorService .execute ({
90- id: Date .now (),
91- url: url ,
92- method: " GET" ,
93- version: " HTTP/1.1" ,
94- })
121+ try {
122+ const { response } = interceptorService .execute ({
123+ id: Date .now (),
124+ url: url ,
125+ method: " GET" ,
126+ version: " HTTP/1.1" ,
127+ })
128+
129+ const res = await response
130+
131+ if (E .isRight (res )) {
132+ const responsePayload = parseBodyAsJSONOrYAML <unknown >(res .right .body )
133+
134+ if (O .isSome (responsePayload )) {
135+ return E .right (JSON .stringify (responsePayload .value ))
136+ }
137+ }
138+
139+ // Return the actual error from the failed request
140+ return E .left (E .isLeft (res ) ? res .left : " REQUEST_FAILED" )
141+ } catch (error ) {
142+ // Return the caught error for proper CORS detection
143+ return E .left (error )
144+ }
145+ }
146+
147+ const retryWithProxy = async () => {
148+ isFetchingUrl .value = true
95149
96- const res = await response
150+ try {
151+ // Store the current interceptor to restore later
152+ const previousInterceptorId = interceptorService .getCurrentId ()
97153
98- if (E .isLeft (res )) {
99- return E .left (" REQUEST_FAILED" )
100- }
154+ // Switch to proxy interceptor
155+ interceptorService .setActive (proxyInterceptorService .id )
101156
102- const responsePayload = parseBodyAsJSONOrYAML <unknown >(res .right .body )
157+ // Retry the request with proxy
158+ const res = await urlFetchLogic (inputChooseGistToImportFrom .value )
103159
104- if ( O . isSome ( responsePayload )) {
105- // stringify the response payload
106- return E . right ( JSON . stringify ( responsePayload . value ) )
160+ // Restore previous interceptor
161+ if ( previousInterceptorId ) {
162+ interceptorService . setActive ( previousInterceptorId )
107163 }
108164
109- return E .left (" REQUEST_FAILED" )
165+ if (E .isRight (res )) {
166+ showCorsError .value = false
167+ emit (" importFromURL" , res .right )
168+ } else {
169+ toast .error (t (" import.failed" ))
170+ }
171+ } catch (error ) {
172+ toast .error (t (" import.failed" ))
173+ } finally {
174+ isFetchingUrl .value = false
110175 }
176+ }
111177
112178async function fetchUrlData() {
113179 isFetchingUrl .value = true
114180 const res = await urlFetchLogic (inputChooseGistToImportFrom .value )
115181
116182 if (E .isLeft (res )) {
117- toast .error (t (" import.failed" ))
183+ // @ts-ignore
184+ if (isCorsError (res .left ?.error )) {
185+ showCorsError .value = true
186+ } else {
187+ toast .error (t (" import.failed" ))
188+ }
118189 isFetchingUrl .value = false
119190 return
120191 }
121192
122193 emit (" importFromURL" , res .right )
123-
124194 isFetchingUrl .value = false
125195}
126196 </script >
0 commit comments