[Chore] All api urls are now using the helper function (#7672)

* fix: logging information about the login

* chore: improving the withBaseURL workflow

* chore: moving VITE_KC_API_BASE_URL to the helper function

* fix: env to helper function api base url

* chore: fixing another api base url

* chore: shortlinks with base api helper function

* chore: prompt edit with base helper function

* fix: auto fmt

* fix: withAPIBaseURL for all urls

* fix: AI caught my typo, RIP

* fix: expected

* fix: renaming this so it is less specific to environment

---------

Co-authored-by: Jace Browning <jacebrowning@gmail.com>
This commit is contained in:
Kevin Nadro
2025-07-03 08:54:03 -05:00
committed by GitHub
parent e5d082f441
commit df6256266c
13 changed files with 104 additions and 46 deletions

View File

@ -4,6 +4,8 @@
URL STATUS URL STATUS
000 https://${BASE_URL} 000 https://${BASE_URL}
405 https://api.dev.zoo.dev/oauth2/token/revoke
401 https://api.dev.zoo.dev/users
301 https://discord.gg/JQEpHR7Nt2 301 https://discord.gg/JQEpHR7Nt2
404 https://github.com/KittyCAD/engine/issues/3528 404 https://github.com/KittyCAD/engine/issues/3528
404 https://github.com/KittyCAD/modeling-app/commit/${ref} 404 https://github.com/KittyCAD/modeling-app/commit/${ref}

View File

@ -7,7 +7,7 @@ import {
LanguageServerClient, LanguageServerClient,
LspWorkerEventType, LspWorkerEventType,
} from '@kittycad/codemirror-lsp-client' } from '@kittycad/codemirror-lsp-client'
import { TEST, VITE_KC_API_BASE_URL } from '@src/env' import { TEST } from '@src/env'
import React, { createContext, useContext, useMemo, useState } from 'react' import React, { createContext, useContext, useMemo, useState } from 'react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import type * as LSP from 'vscode-languageserver-protocol' import type * as LSP from 'vscode-languageserver-protocol'
@ -28,6 +28,7 @@ import type { FileEntry } from '@src/lib/project'
import { codeManager } from '@src/lib/singletons' import { codeManager } from '@src/lib/singletons'
import { err } from '@src/lib/trap' import { err } from '@src/lib/trap'
import { useToken } from '@src/lib/singletons' import { useToken } from '@src/lib/singletons'
import { withAPIBaseURL } from '@src/lib/withBaseURL'
function getWorkspaceFolders(): LSP.WorkspaceFolder[] { function getWorkspaceFolders(): LSP.WorkspaceFolder[] {
return [] return []
@ -85,7 +86,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
const initEvent: KclWorkerOptions = { const initEvent: KclWorkerOptions = {
wasmUrl: wasmUrl(), wasmUrl: wasmUrl(),
token: token, token: token,
apiBaseUrl: VITE_KC_API_BASE_URL, apiBaseUrl: withAPIBaseURL(''),
} }
lspWorker.postMessage({ lspWorker.postMessage({
worker: LspWorker.Kcl, worker: LspWorker.Kcl,
@ -178,7 +179,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
const initEvent: CopilotWorkerOptions = { const initEvent: CopilotWorkerOptions = {
wasmUrl: wasmUrl(), wasmUrl: wasmUrl(),
token: token, token: token,
apiBaseUrl: VITE_KC_API_BASE_URL, apiBaseUrl: withAPIBaseURL(''),
} }
lspWorker.postMessage({ lspWorker.postMessage({
worker: LspWorker.Copilot, worker: LspWorker.Copilot,

View File

@ -1,4 +1,3 @@
import { VITE_KC_API_BASE_URL } from '@src/env'
import { UAParser } from 'ua-parser-js' import { UAParser } from 'ua-parser-js'
import type { OsInfo } from '@rust/kcl-lib/bindings/OsInfo' import type { OsInfo } from '@rust/kcl-lib/bindings/OsInfo'
@ -11,6 +10,7 @@ import { isDesktop } from '@src/lib/isDesktop'
import type RustContext from '@src/lib/rustContext' import type RustContext from '@src/lib/rustContext'
import screenshot from '@src/lib/screenshot' import screenshot from '@src/lib/screenshot'
import { APP_VERSION } from '@src/routes/utils' import { APP_VERSION } from '@src/routes/utils'
import { withAPIBaseURL } from '@src/lib/withBaseURL'
/* eslint-disable suggest-no-throw/suggest-no-throw -- /* eslint-disable suggest-no-throw/suggest-no-throw --
* All the throws in CoreDumpManager are intentional and should be caught and handled properly * All the throws in CoreDumpManager are intentional and should be caught and handled properly
@ -35,7 +35,7 @@ export class CoreDumpManager {
codeManager: CodeManager codeManager: CodeManager
rustContext: RustContext rustContext: RustContext
token: string | undefined token: string | undefined
baseUrl: string = VITE_KC_API_BASE_URL baseUrl: string = withAPIBaseURL('')
constructor( constructor(
engineCommandManager: EngineCommandManager, engineCommandManager: EngineCommandManager,

View File

@ -26,6 +26,7 @@ import { err } from '@src/lib/trap'
import type { DeepPartial } from '@src/lib/types' import type { DeepPartial } from '@src/lib/types'
import { getInVariableCase } from '@src/lib/utils' import { getInVariableCase } from '@src/lib/utils'
import { IS_STAGING } from '@src/routes/utils' import { IS_STAGING } from '@src/routes/utils'
import { withAPIBaseURL } from '@src/lib/withBaseURL'
export async function renameProjectDirectory( export async function renameProjectDirectory(
projectPath: string, projectPath: string,
@ -697,7 +698,9 @@ export const readTokenFile = async () => {
export const writeTokenFile = async (token: string) => { export const writeTokenFile = async (token: string) => {
const tokenFilePath = await getTokenFilePath() const tokenFilePath = await getTokenFilePath()
if (err(token)) return Promise.reject(token) if (err(token)) return Promise.reject(token)
return window.electron.writeFile(tokenFilePath, token) const result = window.electron.writeFile(tokenFilePath, token)
console.log('token written to disk')
return result
} }
export const writeTelemetryFile = async (content: string) => { export const writeTelemetryFile = async (content: string) => {
@ -722,12 +725,9 @@ export const setState = async (state: Project | undefined): Promise<void> => {
appStateStore = state appStateStore = state
} }
export const getUser = async ( export const getUser = async (token: string): Promise<Models['User_type']> => {
token: string,
hostname: string
): Promise<Models['User_type']> => {
try { try {
const user = await fetch(`${hostname}/users/me`, { const user = await fetch(withAPIBaseURL('/users/me'), {
headers: new Headers({ headers: new Headers({
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
}), }),

View File

@ -1,4 +1,4 @@
import { VITE_KC_API_BASE_URL, VITE_KC_SITE_APP_URL } from '@src/env' import { VITE_KC_SITE_APP_URL } from '@src/env'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import { stringToBase64 } from '@src/lib/base64' import { stringToBase64 } from '@src/lib/base64'
@ -7,6 +7,7 @@ import {
CREATE_FILE_URL_PARAM, CREATE_FILE_URL_PARAM,
} from '@src/lib/constants' } from '@src/lib/constants'
import { err } from '@src/lib/trap' import { err } from '@src/lib/trap'
import { withAPIBaseURL } from '@src/lib/withBaseURL'
export interface FileLinkParams { export interface FileLinkParams {
code: string code: string
@ -96,7 +97,7 @@ export async function createShortlink(
if (password) { if (password) {
body.password = password body.password = password
} }
const response = await fetch(`${VITE_KC_API_BASE_URL}/user/shortlinks`, { const response = await fetch(withAPIBaseURL('/user/shortlinks'), {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-type': 'application/json', 'Content-type': 'application/json',

View File

@ -1,7 +1,7 @@
import type { SelectionRange } from '@codemirror/state' import type { SelectionRange } from '@codemirror/state'
import { EditorSelection, Transaction } from '@codemirror/state' import { EditorSelection, Transaction } from '@codemirror/state'
import type { Models } from '@kittycad/lib' import type { Models } from '@kittycad/lib'
import { VITE_KC_API_BASE_URL, VITE_KC_SITE_BASE_URL } from '@src/env' import { VITE_KC_SITE_BASE_URL } from '@src/env'
import { diffLines } from 'diff' import { diffLines } from 'diff'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import type { TextToCadMultiFileIteration_type } from '@kittycad/lib/dist/types/src/models' import type { TextToCadMultiFileIteration_type } from '@kittycad/lib/dist/types/src/models'
@ -28,6 +28,7 @@ import { uuidv4 } from '@src/lib/utils'
import type { File as KittyCadLibFile } from '@kittycad/lib/dist/types/src/models' import type { File as KittyCadLibFile } from '@kittycad/lib/dist/types/src/models'
import type { FileMeta } from '@src/lib/types' import type { FileMeta } from '@src/lib/types'
import type { RequestedKCLFile } from '@src/machines/systemIO/utils' import type { RequestedKCLFile } from '@src/machines/systemIO/utils'
import { withAPIBaseURL } from '@src/lib/withBaseURL'
type KclFileMetaMap = { type KclFileMetaMap = {
[execStateFileNamesIndex: number]: Extract<FileMeta, { type: 'kcl' }> [execStateFileNamesIndex: number]: Extract<FileMeta, { type: 'kcl' }>
@ -77,7 +78,7 @@ async function submitTextToCadRequest(
}) })
const response = await fetch( const response = await fetch(
`${VITE_KC_API_BASE_URL}/ml/text-to-cad/multi-file/iteration`, withAPIBaseURL('/ml/text-to-cad/multi-file/iteration'),
{ {
method: 'POST', method: 'POST',
headers: { headers: {
@ -304,7 +305,7 @@ export async function getPromptToEditResult(
id: string, id: string,
token?: string token?: string
): Promise<Models['TextToCadMultiFileIteration_type'] | Error> { ): Promise<Models['TextToCadMultiFileIteration_type'] | Error> {
const url = VITE_KC_API_BASE_URL + '/async/operations/' + id const url = withAPIBaseURL(`/async/operations/${id}`)
const data: Models['TextToCadMultiFileIteration_type'] | Error = const data: Models['TextToCadMultiFileIteration_type'] | Error =
await crossPlatformFetch( await crossPlatformFetch(
url, url,
@ -340,7 +341,7 @@ export async function doPromptEdit({
;(window as any).process = { ;(window as any).process = {
env: { env: {
ZOO_API_TOKEN: token, ZOO_API_TOKEN: token,
ZOO_HOST: VITE_KC_API_BASE_URL, ZOO_HOST: withAPIBaseURL(''),
}, },
} }
try { try {

View File

@ -1,4 +1,4 @@
import { VITE_KC_API_BASE_URL } from '@src/env' import { withAPIBaseURL } from '@src/lib/withBaseURL'
import EditorManager from '@src/editor/manager' import EditorManager from '@src/editor/manager'
import { KclManager } from '@src/lang/KclSingleton' import { KclManager } from '@src/lang/KclSingleton'
@ -171,7 +171,7 @@ const appMachine = setup({
systemId: BILLING, systemId: BILLING,
input: { input: {
...BILLING_CONTEXT_DEFAULTS, ...BILLING_CONTEXT_DEFAULTS,
urlUserService: VITE_KC_API_BASE_URL, urlUserService: withAPIBaseURL(''),
}, },
}), }),
], ],

View File

@ -1,5 +1,4 @@
import type { Models } from '@kittycad/lib' import type { Models } from '@kittycad/lib'
import { VITE_KC_API_BASE_URL } from '@src/env'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import type { NavigateFunction } from 'react-router-dom' import type { NavigateFunction } from 'react-router-dom'
import { import {
@ -19,6 +18,7 @@ import { err, reportRejection } from '@src/lib/trap'
import { toSync } from '@src/lib/utils' import { toSync } from '@src/lib/utils'
import { getAllSubDirectoriesAtProjectRoot } from '@src/machines/systemIO/snapshotContext' import { getAllSubDirectoriesAtProjectRoot } from '@src/machines/systemIO/snapshotContext'
import { joinOSPaths } from '@src/lib/paths' import { joinOSPaths } from '@src/lib/paths'
import { withAPIBaseURL } from '@src/lib/withBaseURL'
export async function submitTextToCadPrompt( export async function submitTextToCadPrompt(
prompt: string, prompt: string,
@ -32,7 +32,7 @@ export async function submitTextToCadPrompt(
kcl_version: kclManager.kclVersion, kcl_version: kclManager.kclVersion,
} }
// Glb has a smaller footprint than gltf, should we want to render it. // Glb has a smaller footprint than gltf, should we want to render it.
const url = VITE_KC_API_BASE_URL + '/ai/text-to-cad/glb?kcl=true' const url = withAPIBaseURL('/ai/text-to-cad/glb?kcl=true')
const data: Models['TextToCad_type'] | Error = await crossPlatformFetch( const data: Models['TextToCad_type'] | Error = await crossPlatformFetch(
url, url,
{ {
@ -58,7 +58,7 @@ export async function getTextToCadResult(
id: string, id: string,
token?: string token?: string
): Promise<Models['TextToCad_type'] | Error> { ): Promise<Models['TextToCad_type'] | Error> {
const url = VITE_KC_API_BASE_URL + '/user/text-to-cad/' + id const url = withAPIBaseURL(`/user/text-to-cad/${id}`)
const data: Models['TextToCad_type'] | Error = await crossPlatformFetch( const data: Models['TextToCad_type'] | Error = await crossPlatformFetch(
url, url,
{ {

View File

@ -1,14 +1,13 @@
import type { Models } from '@kittycad/lib/dist/types/src' import type { Models } from '@kittycad/lib/dist/types/src'
import { VITE_KC_API_BASE_URL } from '@src/env'
import crossPlatformFetch from '@src/lib/crossPlatformFetch' import crossPlatformFetch from '@src/lib/crossPlatformFetch'
import { withAPIBaseURL } from '@src/lib/withBaseURL'
export async function sendTelemetry( export async function sendTelemetry(
id: string, id: string,
feedback: Models['MlFeedback_type'], feedback: Models['MlFeedback_type'],
token?: string token?: string
): Promise<void> { ): Promise<void> {
const url = const url = withAPIBaseURL(`/user/text-to-cad/${id}?feedback=${feedback}`)
VITE_KC_API_BASE_URL + '/user/text-to-cad/' + id + '?feedback=' + feedback
await crossPlatformFetch( await crossPlatformFetch(
url, url,
{ {

View File

@ -0,0 +1,34 @@
import { withAPIBaseURL } from '@src/lib/withBaseURL'
describe('withBaseURL', () => {
/**
* running in the development environment
* the .env.development should load
*/
describe('withAPIBaseUrl', () => {
it('should return base url', () => {
const expected = 'https://api.dev.zoo.dev'
const actual = withAPIBaseURL('')
expect(actual).toBe(expected)
})
it('should return base url with /users', () => {
const expected = 'https://api.dev.zoo.dev/users'
const actual = withAPIBaseURL('/users')
expect(actual).toBe(expected)
})
it('should return a longer base url with /oauth2/token/revoke', () => {
const expected = 'https://api.dev.zoo.dev/oauth2/token/revoke'
const actual = withAPIBaseURL('/oauth2/token/revoke')
expect(actual).toBe(expected)
})
it('should ensure base url does not have ending slash', () => {
const expected = 'https://api.dev.zoo.dev'
const actual = withAPIBaseURL('')
expect(actual).toBe(expected)
const expectedEndsWith = expected[expected.length - 1]
const actualEndsWith = actual[actual.length - 1]
expect(actual).toBe(expected)
expect(actualEndsWith).toBe(expectedEndsWith)
})
})
})

View File

@ -1,5 +1,5 @@
import { VITE_KC_API_BASE_URL } from '@src/env' import { VITE_KC_API_BASE_URL } from '@src/env'
export default function withBaseUrl(path: string): string { export function withAPIBaseURL(path: string): string {
return VITE_KC_API_BASE_URL + path return VITE_KC_API_BASE_URL + path
} }

View File

@ -1,5 +1,5 @@
import type { Models } from '@kittycad/lib' import type { Models } from '@kittycad/lib'
import { VITE_KC_API_BASE_URL, VITE_KC_DEV_TOKEN } from '@src/env' import { VITE_KC_DEV_TOKEN } from '@src/env'
import { assign, fromPromise, setup } from 'xstate' import { assign, fromPromise, setup } from 'xstate'
import { COOKIE_NAME, OAUTH2_DEVICE_CLIENT_ID } from '@src/lib/constants' import { COOKIE_NAME, OAUTH2_DEVICE_CLIENT_ID } from '@src/lib/constants'
@ -10,10 +10,7 @@ import {
} from '@src/lib/desktop' } from '@src/lib/desktop'
import { isDesktop } from '@src/lib/isDesktop' import { isDesktop } from '@src/lib/isDesktop'
import { markOnce } from '@src/lib/performance' import { markOnce } from '@src/lib/performance'
import { import { withAPIBaseURL } from '@src/lib/withBaseURL'
default as withBaseURL,
default as withBaseUrl,
} from '@src/lib/withBaseURL'
import { ACTOR_IDS } from '@src/machines/machineConstants' import { ACTOR_IDS } from '@src/machines/machineConstants'
export interface UserContext { export interface UserContext {
@ -31,11 +28,21 @@ export type Events =
} }
export const TOKEN_PERSIST_KEY = 'TOKEN_PERSIST_KEY' export const TOKEN_PERSIST_KEY = 'TOKEN_PERSIST_KEY'
/**
* Determine which token do we have persisted to initialize the auth machine
*/
const persistedCookie = getCookie(COOKIE_NAME)
const persistedLocalStorage = localStorage?.getItem(TOKEN_PERSIST_KEY) || ''
const persistedDevToken = VITE_KC_DEV_TOKEN
export const persistedToken = export const persistedToken =
VITE_KC_DEV_TOKEN || persistedDevToken || persistedCookie || persistedLocalStorage
getCookie(COOKIE_NAME) || console.log('Initial persisted token')
localStorage?.getItem(TOKEN_PERSIST_KEY) || console.table([
'' ['cookie', !!persistedCookie],
['local storage', !!persistedLocalStorage],
['api token', !!persistedDevToken],
])
export const authMachine = setup({ export const authMachine = setup({
types: {} as { types: {} as {
@ -132,7 +139,7 @@ export const authMachine = setup({
async function getUser(input: { token?: string }) { async function getUser(input: { token?: string }) {
const token = await getAndSyncStoredToken(input) const token = await getAndSyncStoredToken(input)
const url = withBaseURL('/user') const url = withAPIBaseURL('/user')
const headers: { [key: string]: string } = { const headers: { [key: string]: string } = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
} }
@ -141,7 +148,7 @@ async function getUser(input: { token?: string }) {
if (token) headers['Authorization'] = `Bearer ${token}` if (token) headers['Authorization'] = `Bearer ${token}`
const userPromise = isDesktop() const userPromise = isDesktop()
? getUserDesktop(token, VITE_KC_API_BASE_URL) ? getUserDesktop(token)
: fetch(url, { : fetch(url, {
method: 'GET', method: 'GET',
credentials: 'include', credentials: 'include',
@ -190,12 +197,24 @@ async function getAndSyncStoredToken(input: {
token?: string token?: string
}): Promise<string> { }): Promise<string> {
// dev mode // dev mode
if (VITE_KC_DEV_TOKEN) return VITE_KC_DEV_TOKEN if (VITE_KC_DEV_TOKEN) {
console.log('Token used for authentication')
console.table([['api token', !!VITE_KC_DEV_TOKEN]])
return VITE_KC_DEV_TOKEN
}
const token = const inputToken = input.token && input.token !== '' ? input.token : ''
input.token && input.token !== '' const cookieToken = getCookie(COOKIE_NAME)
? input.token const localStorageToken = localStorage?.getItem(TOKEN_PERSIST_KEY) || ''
: getCookie(COOKIE_NAME) || localStorage?.getItem(TOKEN_PERSIST_KEY) || '' const token = inputToken || cookieToken || localStorageToken
console.log('Token used for authentication')
console.table([
['persisted token', !!inputToken],
['cookie', !!cookieToken],
['local storage', !!localStorageToken],
['api token', !!VITE_KC_DEV_TOKEN],
])
if (token) { if (token) {
// has just logged in, update storage // has just logged in, update storage
localStorage.setItem(TOKEN_PERSIST_KEY, token) localStorage.setItem(TOKEN_PERSIST_KEY, token)
@ -221,7 +240,7 @@ async function logout() {
if (token) { if (token) {
try { try {
await fetch(withBaseUrl('/oauth2/token/revoke'), { await fetch(withAPIBaseURL('/oauth2/token/revoke'), {
method: 'POST', method: 'POST',
credentials: 'include', credentials: 'include',
headers: { headers: {
@ -244,7 +263,7 @@ async function logout() {
} }
} }
return fetch(withBaseUrl('/logout'), { return fetch(withAPIBaseURL('/logout'), {
method: 'POST', method: 'POST',
credentials: 'include', credentials: 'include',
}) })

View File

@ -6,7 +6,7 @@ import { Link } from 'react-router-dom'
import { ActionButton } from '@src/components/ActionButton' import { ActionButton } from '@src/components/ActionButton'
import { CustomIcon } from '@src/components/CustomIcon' import { CustomIcon } from '@src/components/CustomIcon'
import { Logo } from '@src/components/Logo' import { Logo } from '@src/components/Logo'
import { VITE_KC_API_BASE_URL, VITE_KC_SITE_BASE_URL } from '@src/env' import { VITE_KC_SITE_BASE_URL } from '@src/env'
import { APP_NAME } from '@src/lib/constants' import { APP_NAME } from '@src/lib/constants'
import { isDesktop } from '@src/lib/isDesktop' import { isDesktop } from '@src/lib/isDesktop'
import { openExternalBrowserIfDesktop } from '@src/lib/openWindow' import { openExternalBrowserIfDesktop } from '@src/lib/openWindow'
@ -15,6 +15,7 @@ import { reportRejection } from '@src/lib/trap'
import { toSync } from '@src/lib/utils' import { toSync } from '@src/lib/utils'
import { authActor, useSettings } from '@src/lib/singletons' import { authActor, useSettings } from '@src/lib/singletons'
import { APP_VERSION, generateSignInUrl } from '@src/routes/utils' import { APP_VERSION, generateSignInUrl } from '@src/routes/utils'
import { withAPIBaseURL } from '@src/lib/withBaseURL'
const subtleBorder = const subtleBorder =
'border border-solid border-chalkboard-30 dark:border-chalkboard-80' 'border border-solid border-chalkboard-30 dark:border-chalkboard-80'
@ -54,7 +55,7 @@ const SignIn = () => {
const signInDesktop = async () => { const signInDesktop = async () => {
// We want to invoke our command to login via device auth. // We want to invoke our command to login via device auth.
const userCodeToDisplay = await window.electron const userCodeToDisplay = await window.electron
.startDeviceFlow(VITE_KC_API_BASE_URL + location.search) .startDeviceFlow(withAPIBaseURL(location.search))
.catch(reportError) .catch(reportError)
if (!userCodeToDisplay) { if (!userCodeToDisplay) {
console.error('No user code received while trying to log in') console.error('No user code received while trying to log in')