-
Notifications
You must be signed in to change notification settings - Fork 2.4k
chore: Phase 1b - move configure-v3-client/insomnia-fetch behind insomnia-api #10329
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
gatzjames
wants to merge
1
commit into
arch-phase-1a-cli-data-layer
from
arch-phase-1b-configure-v3-client-boundary
Draft
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| import { platform } from 'insomnia-data/common'; | ||
|
|
||
| import { version } from '../package.json'; | ||
|
|
||
| interface ClientEnv { | ||
| PLAYWRIGHT_TEST: string | undefined; | ||
| INSOMNIA_ENV: string | undefined; | ||
| INSOMNIA_API_URL: string | undefined; | ||
| } | ||
|
|
||
| // Renderer reads env from the preload (`window.env`); main process, UtilityProcess and the inso | ||
| // CLI have no `window` and fall back to `process.env`. | ||
| const env: ClientEnv = | ||
| typeof window !== 'undefined' && (window as unknown as { env?: ClientEnv }).env | ||
| ? (window as unknown as { env: ClientEnv }).env | ||
| : (process.env as unknown as ClientEnv); | ||
|
|
||
| export const PLAYWRIGHT_TEST = env.PLAYWRIGHT_TEST; | ||
|
|
||
| export const INSOMNIA_FETCH_TIME_OUT = 30_000; | ||
|
|
||
| export const getApiBaseURL = () => env.INSOMNIA_API_URL || 'https://api.insomnia.rest'; | ||
|
|
||
| const getAppEnvironment = () => env.INSOMNIA_ENV || process.env.INSOMNIA_ENV || 'production'; | ||
|
|
||
| // All workspace packages in this monorepo are released with the same version number. | ||
| const getAppVersion = () => version; | ||
|
|
||
| export const getClientString = () => `${getAppEnvironment()}::${platform}::${getAppVersion()}`; | ||
|
|
||
| export const generateRequestId = (prefix: string) => `${prefix}_${crypto.randomUUID().replace(/-/g, '')}`; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| import { generateRequestId, getApiBaseURL, getClientString } from './client-defaults'; | ||
| import { proxyAwareFetch } from './insomnia-fetch'; | ||
| import { configureV3Client } from './spaces'; | ||
|
|
||
| export const configureV3ClientDefaults = () => | ||
| configureV3Client({ | ||
| getBaseURL: getApiBaseURL, | ||
| getClientString, | ||
| generateRequestId: () => generateRequestId('desk'), | ||
| fetchApi: proxyAwareFetch, | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| import { generateRequestId, getApiBaseURL, getClientString, INSOMNIA_FETCH_TIME_OUT, PLAYWRIGHT_TEST } from './client-defaults'; | ||
| import { type FetchConfig, ResponseFailError } from './fetch'; | ||
|
|
||
| type FetchImplementation = (input: string, init?: RequestInit) => Promise<Response>; | ||
|
|
||
| // node fetch ignores the system proxy and OS certs — main swaps in net.fetch (entry.main.ts) | ||
| let fetchImpl: FetchImplementation = (input, init) => globalThis.fetch(input, init); | ||
|
|
||
| export function setFetchImplementation(impl: FetchImplementation) { | ||
| fetchImpl = impl; | ||
| } | ||
|
|
||
| // Stable, proxy-aware fetch handle for callers that need a raw `fetch` (e.g. the v3 SDK's | ||
| // Configuration.fetchApi). It delegates to the current `fetchImpl` on every call rather than | ||
| // capturing it, so it works regardless of whether setFetchImplementation() has run yet. | ||
| export const proxyAwareFetch: typeof globalThis.fetch = (input, init) => | ||
| fetchImpl(input as string, init as RequestInit | undefined); | ||
|
|
||
| // Adds headers, retries and opens deep links returned from the api | ||
| export async function insomniaFetch<T = void>({ | ||
| method, | ||
| path, | ||
| data, | ||
| sessionId, | ||
| organizationId, | ||
| origin, | ||
| headers, | ||
| timeout = INSOMNIA_FETCH_TIME_OUT, | ||
| onDeepLink, | ||
| }: FetchConfig & { | ||
| // It's not used at all, should be removed? | ||
| retries?: number; | ||
| onDeepLink?: (uri: string) => void; | ||
| }): Promise<T> { | ||
| const config: RequestInit = { | ||
| method, | ||
| headers: { | ||
| ...headers, | ||
| 'X-Insomnia-Client': getClientString(), | ||
| 'insomnia-request-id': generateRequestId('desk'), | ||
| 'X-Origin': origin || getApiBaseURL(), | ||
| ...(sessionId ? { 'X-Session-Id': sessionId } : {}), | ||
| ...(data ? { 'Content-Type': 'application/json' } : {}), | ||
| ...(organizationId ? { 'X-Insomnia-Org-Id': organizationId } : {}), | ||
| ...(PLAYWRIGHT_TEST ? { 'X-Mockbin-Test': 'true' } : {}), | ||
| }, | ||
| ...(data ? { body: JSON.stringify(data) } : {}), | ||
| signal: AbortSignal.timeout(timeout), | ||
| }; | ||
| if (sessionId === undefined) { | ||
| throw new Error(`No session ID provided to ${method}:${path}`); | ||
| } | ||
|
|
||
| try { | ||
| const response = await fetchImpl((origin || getApiBaseURL()) + path, config); | ||
| const uri = response.headers.get('x-insomnia-command'); | ||
| if (uri && onDeepLink) { | ||
| onDeepLink(uri); | ||
| } | ||
| const isJson = response.headers.get('content-type')?.includes('application/json') || path.match(/\.json$/); | ||
| if (!response.ok) { | ||
| let errName = `CODE-${response.status}`; | ||
| let errMsg = response.statusText; | ||
| if (isJson) { | ||
| try { | ||
| const json = await response.json(); | ||
| if (typeof json?.error === 'string') { | ||
| errName = json.error; | ||
| } | ||
| if (typeof json?.message === 'string') { | ||
| errMsg = json.message; | ||
| } | ||
| } catch {} | ||
| } | ||
| throw new ResponseFailError(errName, errMsg, response); | ||
| } | ||
| return isJson ? response.json() : (response.text() as Promise<T>); | ||
| } catch (err) { | ||
| if (!(err instanceof Error)) { | ||
| throw err; | ||
| } | ||
| // AbortSignal.timeout() gives TimeoutError, not AbortError | ||
| if (err.name === 'AbortError' || err.name === 'TimeoutError') { | ||
| throw new Error(`insomniaFetch timed out: ${method} ${path}`, { cause: err }); | ||
| } | ||
| // the real error (ECONNREFUSED, cert problems) hides in err.cause, sometimes nested in an AggregateError | ||
| const cause = (err as { cause?: string | { code?: string; message?: string; errors?: { code?: string }[] } }) | ||
| .cause; | ||
| const detail = typeof cause === 'string' ? cause : cause?.code || cause?.errors?.[0]?.code || cause?.message; | ||
| if (detail) { | ||
| // fresh Error (don't mutate err.message) so a re-observed/retried error doesn't append the detail twice | ||
| throw new Error(`${err.message} (${detail})`, { cause: err }); | ||
| } | ||
| throw err; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,21 +1 @@ | ||
| import { configureV3Client } from 'insomnia-api'; | ||
|
|
||
| import { getApiBaseURL, getClientString } from './constants'; | ||
| import { proxyAwareFetch } from './insomnia-fetch'; | ||
| import { generateId } from './misc'; | ||
|
|
||
| /** | ||
| * Wires the v3 API client with the standard desktop/CLI configuration. The inputs are identical | ||
| * across every entrypoint (renderer, main, CLI), so this lives in one place to keep the request-id | ||
| * prefix and base-URL wiring from drifting between them. Unlike `configureFetch`, which injects | ||
| * entrypoint-specific behaviour (e.g. deep-link handling) and is wired inline per entrypoint. | ||
| */ | ||
| export const configureV3ClientDefaults = () => | ||
| configureV3Client({ | ||
| getBaseURL: getApiBaseURL, | ||
| getClientString, | ||
| generateRequestId: () => generateId('desk'), | ||
| // Mirrors `configureFetch` above: route SDK requests through the same proxy-aware fetch | ||
| // (net.fetch in main) so system proxy settings work like they did before the v3 migration. | ||
| fetchApi: proxyAwareFetch, | ||
| }); | ||
| export { configureV3ClientDefaults } from 'insomnia-api'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,98 +1 @@ | ||
| import { type FetchConfig, ResponseFailError } from 'insomnia-api'; | ||
|
|
||
| import { getApiBaseURL, getClientString, INSOMNIA_FETCH_TIME_OUT, PLAYWRIGHT_TEST } from './constants'; | ||
| import { generateId } from './misc'; | ||
|
|
||
| type FetchImplementation = (input: string, init?: RequestInit) => Promise<Response>; | ||
|
|
||
| // node fetch ignores the system proxy and OS certs — main swaps in net.fetch (entry.main.ts) | ||
| let fetchImpl: FetchImplementation = (input, init) => globalThis.fetch(input, init); | ||
|
|
||
| export function setFetchImplementation(impl: FetchImplementation) { | ||
| fetchImpl = impl; | ||
| } | ||
|
|
||
| // Stable, proxy-aware fetch handle for callers that need a raw `fetch` (e.g. the v3 SDK's | ||
| // Configuration.fetchApi). It delegates to the current `fetchImpl` on every call rather than | ||
| // capturing it, so it works regardless of whether setFetchImplementation() has run yet. | ||
| export const proxyAwareFetch: typeof globalThis.fetch = (input, init) => | ||
| fetchImpl(input as string, init as RequestInit | undefined); | ||
|
|
||
| // Adds headers, retries and opens deep links returned from the api | ||
| export async function insomniaFetch<T = void>({ | ||
| method, | ||
| path, | ||
| data, | ||
| sessionId, | ||
| organizationId, | ||
| origin, | ||
| headers, | ||
| timeout = INSOMNIA_FETCH_TIME_OUT, | ||
| onDeepLink, | ||
| }: FetchConfig & { | ||
| // It's not used at all, should be removed? | ||
| retries?: number; | ||
| onDeepLink?: (uri: string) => void; | ||
| }): Promise<T> { | ||
| const config: RequestInit = { | ||
| method, | ||
| headers: { | ||
| ...headers, | ||
| 'X-Insomnia-Client': getClientString(), | ||
| 'insomnia-request-id': generateId('desk'), | ||
| 'X-Origin': origin || getApiBaseURL(), | ||
| ...(sessionId ? { 'X-Session-Id': sessionId } : {}), | ||
| ...(data ? { 'Content-Type': 'application/json' } : {}), | ||
| ...(organizationId ? { 'X-Insomnia-Org-Id': organizationId } : {}), | ||
| ...(PLAYWRIGHT_TEST ? { 'X-Mockbin-Test': 'true' } : {}), | ||
| }, | ||
| ...(data ? { body: JSON.stringify(data) } : {}), | ||
| signal: AbortSignal.timeout(timeout), | ||
| }; | ||
| if (sessionId === undefined) { | ||
| throw new Error(`No session ID provided to ${method}:${path}`); | ||
| } | ||
|
|
||
| try { | ||
| const response = await fetchImpl((origin || getApiBaseURL()) + path, config); | ||
| const uri = response.headers.get('x-insomnia-command'); | ||
| if (uri && onDeepLink) { | ||
| onDeepLink(uri); | ||
| } | ||
| const isJson = response.headers.get('content-type')?.includes('application/json') || path.match(/\.json$/); | ||
| if (!response.ok) { | ||
| let errName = `CODE-${response.status}`; | ||
| let errMsg = response.statusText; | ||
| if (isJson) { | ||
| try { | ||
| const json = await response.json(); | ||
| if (typeof json?.error === 'string') { | ||
| errName = json.error; | ||
| } | ||
| if (typeof json?.message === 'string') { | ||
| errMsg = json.message; | ||
| } | ||
| } catch {} | ||
| } | ||
| throw new ResponseFailError(errName, errMsg, response); | ||
| } | ||
| return isJson ? response.json() : (response.text() as Promise<T>); | ||
| } catch (err) { | ||
| if (!(err instanceof Error)) { | ||
| throw err; | ||
| } | ||
| // AbortSignal.timeout() gives TimeoutError, not AbortError | ||
| if (err.name === 'AbortError' || err.name === 'TimeoutError') { | ||
| throw new Error(`insomniaFetch timed out: ${method} ${path}`, { cause: err }); | ||
| } | ||
| // the real error (ECONNREFUSED, cert problems) hides in err.cause, sometimes nested in an AggregateError | ||
| const cause = (err as { cause?: string | { code?: string; message?: string; errors?: { code?: string }[] } }) | ||
| .cause; | ||
| const detail = typeof cause === 'string' ? cause : cause?.code || cause?.errors?.[0]?.code || cause?.message; | ||
| if (detail) { | ||
| // fresh Error (don't mutate err.message) so a re-observed/retried error doesn't append the detail twice | ||
| throw new Error(`${err.message} (${detail})`, { cause: err }); | ||
| } | ||
| throw err; | ||
| } | ||
| } | ||
| export { insomniaFetch, proxyAwareFetch, setFetchImplementation } from 'insomnia-api'; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we should move insomnia-fetch into insomnia-api since fetch is runtime-related and insomnia-api is runtime-agnostic. What do you think?