diff --git a/interface.d.ts b/interface.d.ts index 1a2904e2b..272da2054 100644 --- a/interface.d.ts +++ b/interface.d.ts @@ -72,7 +72,6 @@ export interface IElectronAPI { } process: { env: { - BASE_URL: string IS_PLAYWRIGHT: string VITE_KC_DEV_TOKEN: string VITE_KC_API_WS_MODELING_URL: string diff --git a/rust/kcl-lib/e2e/executor/outputs/helix_defaults_negative_extrude_output.step b/rust/kcl-lib/e2e/executor/outputs/helix_defaults_negative_extrude_output.step index 7b62dba36..c54ab21d7 100644 --- a/rust/kcl-lib/e2e/executor/outputs/helix_defaults_negative_extrude_output.step +++ b/rust/kcl-lib/e2e/executor/outputs/helix_defaults_negative_extrude_output.step @@ -10,71 +10,76 @@ DATA; NAMED_UNIT(*) SI_UNIT($, .METRE.) ); -#2 = UNCERTAINTY_MEASURE_WITH_UNIT(0.00001, #1, 'DISTANCE_ACCURACY_VALUE', $); -#3 = ( +#2 = ( + NAMED_UNIT(*) + PLANE_ANGLE_UNIT() + SI_UNIT($, .RADIAN.) +); +#3 = UNCERTAINTY_MEASURE_WITH_UNIT(0.00001, #1, 'DISTANCE_ACCURACY_VALUE', $); +#4 = ( GEOMETRIC_REPRESENTATION_CONTEXT(3) - GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#2)) - GLOBAL_UNIT_ASSIGNED_CONTEXT((#1)) + GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#3)) + GLOBAL_UNIT_ASSIGNED_CONTEXT((#1, #2)) REPRESENTATION_CONTEXT('', '3D') ); -#4 = CARTESIAN_POINT('NONE', (0.015, -0.01, -0.005)); -#5 = VERTEX_POINT('NONE', #4); -#6 = CARTESIAN_POINT('NONE', (0.015, 0, -0.005)); -#7 = VERTEX_POINT('NONE', #6); -#8 = DIRECTION('NONE', (1, 0, -0)); -#9 = DIRECTION('NONE', (0, 1, 0)); -#10 = CARTESIAN_POINT('NONE', (0.005, -0.01, -0.005)); -#11 = AXIS2_PLACEMENT_3D('NONE', #10, #9, #8); -#12 = CIRCLE('NONE', #11, 0.01); -#13 = DIRECTION('NONE', (0, 1, 0)); -#14 = VECTOR('NONE', #13, 1); -#15 = CARTESIAN_POINT('NONE', (0.015, -0.01, -0.005)); -#16 = LINE('NONE', #15, #14); -#17 = DIRECTION('NONE', (1, 0, -0)); -#18 = DIRECTION('NONE', (0, 1, 0)); -#19 = CARTESIAN_POINT('NONE', (0.005, 0, -0.005)); -#20 = AXIS2_PLACEMENT_3D('NONE', #19, #18, #17); -#21 = CIRCLE('NONE', #20, 0.01); -#22 = EDGE_CURVE('NONE', #5, #5, #12, .T.); -#23 = EDGE_CURVE('NONE', #5, #7, #16, .T.); -#24 = EDGE_CURVE('NONE', #7, #7, #21, .T.); -#25 = CARTESIAN_POINT('NONE', (0.005, -0.005, -0.005)); -#26 = DIRECTION('NONE', (0, 1, 0)); -#27 = DIRECTION('NONE', (1, 0, -0)); -#28 = AXIS2_PLACEMENT_3D('NONE', #25, #26, #27); -#29 = CYLINDRICAL_SURFACE('NONE', #28, 0.01); -#30 = CARTESIAN_POINT('NONE', (0, -0.01, -0)); -#31 = DIRECTION('NONE', (0, 1, 0)); -#32 = AXIS2_PLACEMENT_3D('NONE', #30, #31, $); -#33 = PLANE('NONE', #32); -#34 = CARTESIAN_POINT('NONE', (0, 0, -0)); -#35 = DIRECTION('NONE', (0, 1, 0)); -#36 = AXIS2_PLACEMENT_3D('NONE', #34, #35, $); -#37 = PLANE('NONE', #36); -#38 = ORIENTED_EDGE('NONE', *, *, #22, .T.); -#39 = ORIENTED_EDGE('NONE', *, *, #24, .F.); -#40 = EDGE_LOOP('NONE', (#38)); -#41 = FACE_BOUND('NONE', #40, .T.); -#42 = EDGE_LOOP('NONE', (#39)); -#43 = FACE_BOUND('NONE', #42, .T.); -#44 = ADVANCED_FACE('NONE', (#41, #43), #29, .T.); -#45 = ORIENTED_EDGE('NONE', *, *, #22, .F.); -#46 = EDGE_LOOP('NONE', (#45)); -#47 = FACE_BOUND('NONE', #46, .T.); -#48 = ADVANCED_FACE('NONE', (#47), #33, .F.); -#49 = ORIENTED_EDGE('NONE', *, *, #24, .T.); -#50 = EDGE_LOOP('NONE', (#49)); -#51 = FACE_BOUND('NONE', #50, .T.); -#52 = ADVANCED_FACE('NONE', (#51), #37, .T.); -#53 = CLOSED_SHELL('NONE', (#44, #48, #52)); -#54 = MANIFOLD_SOLID_BREP('NONE', #53); -#55 = APPLICATION_CONTEXT('configuration controlled 3D design of mechanical parts and assemblies'); -#56 = PRODUCT_DEFINITION_CONTEXT('part definition', #55, 'design'); -#57 = PRODUCT('UNIDENTIFIED_PRODUCT', 'NONE', $, ()); -#58 = PRODUCT_DEFINITION_FORMATION('', $, #57); -#59 = PRODUCT_DEFINITION('design', $, #58, #56); -#60 = PRODUCT_DEFINITION_SHAPE('NONE', $, #59); -#61 = ADVANCED_BREP_SHAPE_REPRESENTATION('NONE', (#54), #3); -#62 = SHAPE_DEFINITION_REPRESENTATION(#60, #61); +#5 = CARTESIAN_POINT('NONE', (0.015, -0.01, -0.005)); +#6 = VERTEX_POINT('NONE', #5); +#7 = CARTESIAN_POINT('NONE', (0.015, 0, -0.005)); +#8 = VERTEX_POINT('NONE', #7); +#9 = DIRECTION('NONE', (1, 0, -0)); +#10 = DIRECTION('NONE', (0, 1, 0)); +#11 = CARTESIAN_POINT('NONE', (0.005, -0.01, -0.005)); +#12 = AXIS2_PLACEMENT_3D('NONE', #11, #10, #9); +#13 = CIRCLE('NONE', #12, 0.01); +#14 = DIRECTION('NONE', (0, 1, 0)); +#15 = VECTOR('NONE', #14, 1); +#16 = CARTESIAN_POINT('NONE', (0.015, -0.01, -0.005)); +#17 = LINE('NONE', #16, #15); +#18 = DIRECTION('NONE', (1, 0, -0)); +#19 = DIRECTION('NONE', (0, 1, 0)); +#20 = CARTESIAN_POINT('NONE', (0.005, 0, -0.005)); +#21 = AXIS2_PLACEMENT_3D('NONE', #20, #19, #18); +#22 = CIRCLE('NONE', #21, 0.01); +#23 = EDGE_CURVE('NONE', #6, #6, #13, .T.); +#24 = EDGE_CURVE('NONE', #6, #8, #17, .T.); +#25 = EDGE_CURVE('NONE', #8, #8, #22, .T.); +#26 = CARTESIAN_POINT('NONE', (0.005, -0.005, -0.005)); +#27 = DIRECTION('NONE', (0, 1, 0)); +#28 = DIRECTION('NONE', (1, 0, -0)); +#29 = AXIS2_PLACEMENT_3D('NONE', #26, #27, #28); +#30 = CYLINDRICAL_SURFACE('NONE', #29, 0.01); +#31 = CARTESIAN_POINT('NONE', (0, -0.01, -0)); +#32 = DIRECTION('NONE', (0, 1, 0)); +#33 = AXIS2_PLACEMENT_3D('NONE', #31, #32, $); +#34 = PLANE('NONE', #33); +#35 = CARTESIAN_POINT('NONE', (0, 0, -0)); +#36 = DIRECTION('NONE', (0, 1, 0)); +#37 = AXIS2_PLACEMENT_3D('NONE', #35, #36, $); +#38 = PLANE('NONE', #37); +#39 = ORIENTED_EDGE('NONE', *, *, #23, .T.); +#40 = ORIENTED_EDGE('NONE', *, *, #25, .F.); +#41 = EDGE_LOOP('NONE', (#39)); +#42 = FACE_BOUND('NONE', #41, .T.); +#43 = EDGE_LOOP('NONE', (#40)); +#44 = FACE_BOUND('NONE', #43, .T.); +#45 = ADVANCED_FACE('NONE', (#42, #44), #30, .T.); +#46 = ORIENTED_EDGE('NONE', *, *, #23, .F.); +#47 = EDGE_LOOP('NONE', (#46)); +#48 = FACE_BOUND('NONE', #47, .T.); +#49 = ADVANCED_FACE('NONE', (#48), #34, .F.); +#50 = ORIENTED_EDGE('NONE', *, *, #25, .T.); +#51 = EDGE_LOOP('NONE', (#50)); +#52 = FACE_BOUND('NONE', #51, .T.); +#53 = ADVANCED_FACE('NONE', (#52), #38, .T.); +#54 = CLOSED_SHELL('NONE', (#45, #49, #53)); +#55 = MANIFOLD_SOLID_BREP('NONE', #54); +#56 = APPLICATION_CONTEXT('configuration controlled 3D design of mechanical parts and assemblies'); +#57 = PRODUCT_DEFINITION_CONTEXT('part definition', #56, 'design'); +#58 = PRODUCT('UNIDENTIFIED_PRODUCT', 'NONE', $, ()); +#59 = PRODUCT_DEFINITION_FORMATION('', $, #58); +#60 = PRODUCT_DEFINITION('design', $, #59, #57); +#61 = PRODUCT_DEFINITION_SHAPE('NONE', $, #60); +#62 = ADVANCED_BREP_SHAPE_REPRESENTATION('NONE', (#55), #4); +#63 = SHAPE_DEFINITION_REPRESENTATION(#61, #62); ENDSEC; END-ISO-10303-21; diff --git a/rust/kcl-lib/src/execution/exec_ast.rs b/rust/kcl-lib/src/execution/exec_ast.rs index f1ee7cc47..0a8b46fb1 100644 --- a/rust/kcl-lib/src/execution/exec_ast.rs +++ b/rust/kcl-lib/src/execution/exec_ast.rs @@ -994,6 +994,39 @@ impl Node { // Check the property and object match -- e.g. ints for arrays, strs for objects. match (object, property, self.computed) { + (KclValue::Plane { value: plane }, Property::String(property), false) => match property.as_str() { + "yAxis" => { + let (p, u) = plane.info.y_axis.as_3_dims(); + Ok(KclValue::array_from_point3d( + p, + NumericType::Known(crate::exec::UnitType::Length(u)), + vec![meta], + )) + } + "xAxis" => { + let (p, u) = plane.info.x_axis.as_3_dims(); + Ok(KclValue::array_from_point3d( + p, + NumericType::Known(crate::exec::UnitType::Length(u)), + vec![meta], + )) + } + "origin" => { + let (p, u) = plane.info.origin.as_3_dims(); + Ok(KclValue::array_from_point3d( + p, + NumericType::Known(crate::exec::UnitType::Length(u)), + vec![meta], + )) + } + other => Err(KclError::new_undefined_value( + KclErrorDetails::new( + format!("Property '{other}' not found in plane"), + vec![self.clone().into()], + ), + None, + )), + }, (KclValue::Object { value: map, meta: _ }, Property::String(property), false) => { if let Some(value) = map.get(&property) { Ok(value.to_owned()) @@ -1013,7 +1046,22 @@ impl Node { vec![self.clone().into()], ))) } - (KclValue::Object { .. }, p, _) => { + (KclValue::Object { value: map, .. }, p @ Property::UInt(i), _) => { + if i == 0 + && let Some(value) = map.get("x") + { + return Ok(value.to_owned()); + } + if i == 1 + && let Some(value) = map.get("y") + { + return Ok(value.to_owned()); + } + if i == 2 + && let Some(value) = map.get("z") + { + return Ok(value.to_owned()); + } let t = p.type_name(); let article = article_for(t); Err(KclError::new_semantic(KclErrorDetails::new( @@ -2205,4 +2253,12 @@ y = x[0mm + 1] "#; parse_execute(ast).await.unwrap_err(); } + + #[tokio::test(flavor = "multi_thread")] + async fn getting_property_of_plane() { + // let ast = include_str!("../../tests/inputs/planestuff.kcl"); + let ast = std::fs::read_to_string("tests/inputs/planestuff.kcl").unwrap(); + + parse_execute(&ast).await.unwrap(); + } } diff --git a/rust/kcl-lib/src/execution/geometry.rs b/rust/kcl-lib/src/execution/geometry.rs index 60ff86fa1..8e8999211 100644 --- a/rust/kcl-lib/src/execution/geometry.rs +++ b/rust/kcl-lib/src/execution/geometry.rs @@ -940,6 +940,12 @@ impl Point3d { units: UnitLen::Unknown, } } + + pub fn as_3_dims(&self) -> ([f64; 3], UnitLen) { + let p = [self.x, self.y, self.z]; + let u = self.units; + (p, u) + } } impl From<[TyF64; 3]> for Point3d { diff --git a/rust/kcl-lib/src/execution/kcl_value.rs b/rust/kcl-lib/src/execution/kcl_value.rs index 9f9a07451..846a6fe73 100644 --- a/rust/kcl-lib/src/execution/kcl_value.rs +++ b/rust/kcl-lib/src/execution/kcl_value.rs @@ -458,6 +458,31 @@ impl KclValue { } } + /// Put the point into a KCL point. + pub fn array_from_point3d(p: [f64; 3], ty: NumericType, meta: Vec) -> Self { + let [x, y, z] = p; + Self::HomArray { + value: vec![ + Self::Number { + value: x, + meta: meta.clone(), + ty, + }, + Self::Number { + value: y, + meta: meta.clone(), + ty, + }, + Self::Number { + value: z, + meta: meta.clone(), + ty, + }, + ], + ty: ty.into(), + } + } + pub(crate) fn as_usize(&self) -> Option { match self { KclValue::Number { value, .. } => crate::try_f64_to_usize(*value), diff --git a/rust/kcl-lib/tests/inputs/planestuff.kcl b/rust/kcl-lib/tests/inputs/planestuff.kcl new file mode 100644 index 000000000..fd0c0946c --- /dev/null +++ b/rust/kcl-lib/tests/inputs/planestuff.kcl @@ -0,0 +1,60 @@ +// There are 3 ways to define a plane in KCL, according to https://zoo.dev/docs/kcl-std/types/std-types-Plane +// - A default plane +// - Modifying a default plane e.g. via offsetPlane +// - Defining your own struct +// This file tests they all work equivalently. + +// Define a plane using struct representation. +myPlane = { + origin = { x = 0, y = 0, z = 0 }, + xAxis = { x = 1, y = 0, z = 0 }, + yAxis = { x = 0, y = 1, z = 0 }, +} + +// Prove we can get its axes and origin. +ax = myPlane.xAxis +assert(ax[0], isEqualTo = 1) +assert(ax[1], isEqualTo = 0) +assert(ax[2], isEqualTo = 0) +ay = myPlane.yAxis +assert(ay[0], isEqualTo = 0) +assert(ay[1], isEqualTo = 1) +assert(ay[2], isEqualTo = 0) +aorigin = myPlane.origin +assert(aorigin[0], isEqualTo = 0) +assert(aorigin[1], isEqualTo = 0) +assert(aorigin[2], isEqualTo = 0) + +// Define a plane using standard planes. +myOtherPlane = XY + +// Prove we can get its axes and origin. +axOther = myOtherPlane.xAxis +assert(axOther[0], isEqualTo = 1) +assert(axOther[1], isEqualTo = 0) +assert(axOther[2], isEqualTo = 0) +ayOther = myOtherPlane.yAxis +assert(ayOther[0], isEqualTo = 0) +assert(ayOther[1], isEqualTo = 1) +assert(ayOther[2], isEqualTo = 0) +aoriginOther = myOtherPlane.origin +assert(aoriginOther[0], isEqualTo = 0) +assert(aoriginOther[1], isEqualTo = 0) +assert(aoriginOther[2], isEqualTo = 0) + +// Define a plane using a plane-modifying function like offsetPlane. +myAlternatePlane = offsetPlane(XY, offset = 0) + +// Prove we can get its axes and origin. +axAlternate = myAlternatePlane.xAxis +assert(axAlternate[0], isEqualTo = 1) +assert(axAlternate[1], isEqualTo = 0) +assert(axAlternate[2], isEqualTo = 0) +ayAlternate = myAlternatePlane.yAxis +assert(ayAlternate[0], isEqualTo = 0) +assert(ayAlternate[1], isEqualTo = 1) +assert(ayAlternate[2], isEqualTo = 0) +aoriginAlternate = myAlternatePlane.origin +assert(aoriginAlternate[0], isEqualTo = 0) +assert(aoriginAlternate[1], isEqualTo = 0) +assert(aoriginAlternate[2], isEqualTo = 0) diff --git a/scripts/known/urls.txt b/scripts/known/urls.txt index 2b08be00e..a4e43158f 100644 --- a/scripts/known/urls.txt +++ b/scripts/known/urls.txt @@ -4,6 +4,8 @@ URL STATUS 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 404 https://github.com/KittyCAD/engine/issues/3528 404 https://github.com/KittyCAD/modeling-app/commit/${ref} diff --git a/src/components/LspProvider.tsx b/src/components/LspProvider.tsx index 5e9155bec..cb3eb6fba 100644 --- a/src/components/LspProvider.tsx +++ b/src/components/LspProvider.tsx @@ -7,7 +7,7 @@ import { LanguageServerClient, LspWorkerEventType, } 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 { useNavigate } from 'react-router-dom' 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 { err } from '@src/lib/trap' import { useToken } from '@src/lib/singletons' +import { withAPIBaseURL } from '@src/lib/withBaseURL' function getWorkspaceFolders(): LSP.WorkspaceFolder[] { return [] @@ -85,7 +86,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => { const initEvent: KclWorkerOptions = { wasmUrl: wasmUrl(), token: token, - apiBaseUrl: VITE_KC_API_BASE_URL, + apiBaseUrl: withAPIBaseURL(''), } lspWorker.postMessage({ worker: LspWorker.Kcl, @@ -178,7 +179,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => { const initEvent: CopilotWorkerOptions = { wasmUrl: wasmUrl(), token: token, - apiBaseUrl: VITE_KC_API_BASE_URL, + apiBaseUrl: withAPIBaseURL(''), } lspWorker.postMessage({ worker: LspWorker.Copilot, diff --git a/src/lib/coredump.ts b/src/lib/coredump.ts index bb88a8637..40040871f 100644 --- a/src/lib/coredump.ts +++ b/src/lib/coredump.ts @@ -1,4 +1,3 @@ -import { VITE_KC_API_BASE_URL } from '@src/env' import { UAParser } from 'ua-parser-js' 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 screenshot from '@src/lib/screenshot' import { APP_VERSION } from '@src/routes/utils' +import { withAPIBaseURL } from '@src/lib/withBaseURL' /* eslint-disable suggest-no-throw/suggest-no-throw -- * All the throws in CoreDumpManager are intentional and should be caught and handled properly @@ -35,7 +35,7 @@ export class CoreDumpManager { codeManager: CodeManager rustContext: RustContext token: string | undefined - baseUrl: string = VITE_KC_API_BASE_URL + baseUrl: string = withAPIBaseURL('') constructor( engineCommandManager: EngineCommandManager, diff --git a/src/lib/desktop.ts b/src/lib/desktop.ts index 3cdba649d..cecba01a8 100644 --- a/src/lib/desktop.ts +++ b/src/lib/desktop.ts @@ -26,6 +26,7 @@ import { err } from '@src/lib/trap' import type { DeepPartial } from '@src/lib/types' import { getInVariableCase } from '@src/lib/utils' import { IS_STAGING } from '@src/routes/utils' +import { withAPIBaseURL } from '@src/lib/withBaseURL' export async function renameProjectDirectory( projectPath: string, @@ -697,7 +698,9 @@ export const readTokenFile = async () => { export const writeTokenFile = async (token: string) => { const tokenFilePath = await getTokenFilePath() 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) => { @@ -722,12 +725,9 @@ export const setState = async (state: Project | undefined): Promise => { appStateStore = state } -export const getUser = async ( - token: string, - hostname: string -): Promise => { +export const getUser = async (token: string): Promise => { try { - const user = await fetch(`${hostname}/users/me`, { + const user = await fetch(withAPIBaseURL('/users/me'), { headers: new Headers({ Authorization: `Bearer ${token}`, }), diff --git a/src/lib/links.ts b/src/lib/links.ts index 8c2b0d599..544fbff19 100644 --- a/src/lib/links.ts +++ b/src/lib/links.ts @@ -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 { stringToBase64 } from '@src/lib/base64' @@ -7,6 +7,7 @@ import { CREATE_FILE_URL_PARAM, } from '@src/lib/constants' import { err } from '@src/lib/trap' +import { withAPIBaseURL } from '@src/lib/withBaseURL' export interface FileLinkParams { code: string @@ -96,7 +97,7 @@ export async function createShortlink( if (password) { body.password = password } - const response = await fetch(`${VITE_KC_API_BASE_URL}/user/shortlinks`, { + const response = await fetch(withAPIBaseURL('/user/shortlinks'), { method: 'POST', headers: { 'Content-type': 'application/json', diff --git a/src/lib/promptToEdit.tsx b/src/lib/promptToEdit.tsx index 5afe6250a..428bd5162 100644 --- a/src/lib/promptToEdit.tsx +++ b/src/lib/promptToEdit.tsx @@ -1,7 +1,7 @@ import type { SelectionRange } from '@codemirror/state' import { EditorSelection, Transaction } from '@codemirror/state' 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 toast from 'react-hot-toast' 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 { FileMeta } from '@src/lib/types' import type { RequestedKCLFile } from '@src/machines/systemIO/utils' +import { withAPIBaseURL } from '@src/lib/withBaseURL' type KclFileMetaMap = { [execStateFileNamesIndex: number]: Extract @@ -77,7 +78,7 @@ async function submitTextToCadRequest( }) 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', headers: { @@ -304,7 +305,7 @@ export async function getPromptToEditResult( id: string, token?: string ): Promise { - const url = VITE_KC_API_BASE_URL + '/async/operations/' + id + const url = withAPIBaseURL(`/async/operations/${id}`) const data: Models['TextToCadMultiFileIteration_type'] | Error = await crossPlatformFetch( url, @@ -340,7 +341,7 @@ export async function doPromptEdit({ ;(window as any).process = { env: { ZOO_API_TOKEN: token, - ZOO_HOST: VITE_KC_API_BASE_URL, + ZOO_HOST: withAPIBaseURL(''), }, } try { diff --git a/src/lib/singletons.ts b/src/lib/singletons.ts index 8eb57da67..5202ed83c 100644 --- a/src/lib/singletons.ts +++ b/src/lib/singletons.ts @@ -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 { KclManager } from '@src/lang/KclSingleton' @@ -171,7 +171,7 @@ const appMachine = setup({ systemId: BILLING, input: { ...BILLING_CONTEXT_DEFAULTS, - urlUserService: VITE_KC_API_BASE_URL, + urlUserService: withAPIBaseURL(''), }, }), ], diff --git a/src/lib/textToCad.ts b/src/lib/textToCad.ts index 7e9f5f4dc..01b0677dc 100644 --- a/src/lib/textToCad.ts +++ b/src/lib/textToCad.ts @@ -1,5 +1,4 @@ import type { Models } from '@kittycad/lib' -import { VITE_KC_API_BASE_URL } from '@src/env' import toast from 'react-hot-toast' import type { NavigateFunction } from 'react-router-dom' import { @@ -19,6 +18,7 @@ import { err, reportRejection } from '@src/lib/trap' import { toSync } from '@src/lib/utils' import { getAllSubDirectoriesAtProjectRoot } from '@src/machines/systemIO/snapshotContext' import { joinOSPaths } from '@src/lib/paths' +import { withAPIBaseURL } from '@src/lib/withBaseURL' export async function submitTextToCadPrompt( prompt: string, @@ -32,7 +32,7 @@ export async function submitTextToCadPrompt( kcl_version: kclManager.kclVersion, } // 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( url, { @@ -58,7 +58,7 @@ export async function getTextToCadResult( id: string, token?: string ): Promise { - 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( url, { diff --git a/src/lib/textToCadTelemetry.ts b/src/lib/textToCadTelemetry.ts index a5c336522..b3d64b0d2 100644 --- a/src/lib/textToCadTelemetry.ts +++ b/src/lib/textToCadTelemetry.ts @@ -1,14 +1,13 @@ 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 { withAPIBaseURL } from '@src/lib/withBaseURL' export async function sendTelemetry( id: string, feedback: Models['MlFeedback_type'], token?: string ): Promise { - const url = - VITE_KC_API_BASE_URL + '/user/text-to-cad/' + id + '?feedback=' + feedback + const url = withAPIBaseURL(`/user/text-to-cad/${id}?feedback=${feedback}`) await crossPlatformFetch( url, { diff --git a/src/lib/withBaseURL.test.ts b/src/lib/withBaseURL.test.ts new file mode 100644 index 000000000..f0a631b77 --- /dev/null +++ b/src/lib/withBaseURL.test.ts @@ -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) + }) + }) +}) diff --git a/src/lib/withBaseURL.ts b/src/lib/withBaseURL.ts index e23436bd3..5eccdff66 100644 --- a/src/lib/withBaseURL.ts +++ b/src/lib/withBaseURL.ts @@ -1,5 +1,5 @@ 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 } diff --git a/src/machines/authMachine.ts b/src/machines/authMachine.ts index 97acd35a4..fcc8c3486 100644 --- a/src/machines/authMachine.ts +++ b/src/machines/authMachine.ts @@ -1,5 +1,5 @@ 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 { COOKIE_NAME, OAUTH2_DEVICE_CLIENT_ID } from '@src/lib/constants' @@ -10,10 +10,7 @@ import { } from '@src/lib/desktop' import { isDesktop } from '@src/lib/isDesktop' import { markOnce } from '@src/lib/performance' -import { - default as withBaseURL, - default as withBaseUrl, -} from '@src/lib/withBaseURL' +import { withAPIBaseURL } from '@src/lib/withBaseURL' import { ACTOR_IDS } from '@src/machines/machineConstants' export interface UserContext { @@ -31,11 +28,21 @@ export type Events = } 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 = - VITE_KC_DEV_TOKEN || - getCookie(COOKIE_NAME) || - localStorage?.getItem(TOKEN_PERSIST_KEY) || - '' + persistedDevToken || persistedCookie || persistedLocalStorage +console.log('Initial persisted token') +console.table([ + ['cookie', !!persistedCookie], + ['local storage', !!persistedLocalStorage], + ['api token', !!persistedDevToken], +]) export const authMachine = setup({ types: {} as { @@ -132,7 +139,7 @@ export const authMachine = setup({ async function getUser(input: { token?: string }) { const token = await getAndSyncStoredToken(input) - const url = withBaseURL('/user') + const url = withAPIBaseURL('/user') const headers: { [key: string]: string } = { 'Content-Type': 'application/json', } @@ -141,7 +148,7 @@ async function getUser(input: { token?: string }) { if (token) headers['Authorization'] = `Bearer ${token}` const userPromise = isDesktop() - ? getUserDesktop(token, VITE_KC_API_BASE_URL) + ? getUserDesktop(token) : fetch(url, { method: 'GET', credentials: 'include', @@ -190,12 +197,24 @@ async function getAndSyncStoredToken(input: { token?: string }): Promise { // 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 = - input.token && input.token !== '' - ? input.token - : getCookie(COOKIE_NAME) || localStorage?.getItem(TOKEN_PERSIST_KEY) || '' + const inputToken = input.token && input.token !== '' ? input.token : '' + const cookieToken = getCookie(COOKIE_NAME) + const localStorageToken = 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) { // has just logged in, update storage localStorage.setItem(TOKEN_PERSIST_KEY, token) @@ -221,7 +240,7 @@ async function logout() { if (token) { try { - await fetch(withBaseUrl('/oauth2/token/revoke'), { + await fetch(withAPIBaseURL('/oauth2/token/revoke'), { method: 'POST', credentials: 'include', headers: { @@ -244,7 +263,7 @@ async function logout() { } } - return fetch(withBaseUrl('/logout'), { + return fetch(withAPIBaseURL('/logout'), { method: 'POST', credentials: 'include', }) diff --git a/src/main.ts b/src/main.ts index c130963b9..cf3533f0f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -70,7 +70,6 @@ dotenv.config({ path: [`.env.${NODE_ENV}.local`, `.env.${NODE_ENV}`] }) // default vite values based on mode process.env.NODE_ENV ??= viteEnv.MODE -process.env.BASE_URL ??= viteEnv.VITE_KC_API_BASE_URL process.env.VITE_KC_API_WS_MODELING_URL ??= viteEnv.VITE_KC_API_WS_MODELING_URL process.env.VITE_KC_API_BASE_URL ??= viteEnv.VITE_KC_API_BASE_URL process.env.VITE_KC_SITE_BASE_URL ??= viteEnv.VITE_KC_SITE_BASE_URL diff --git a/src/routes/SignIn.tsx b/src/routes/SignIn.tsx index 595c16609..6dc269b68 100644 --- a/src/routes/SignIn.tsx +++ b/src/routes/SignIn.tsx @@ -6,7 +6,7 @@ import { Link } from 'react-router-dom' import { ActionButton } from '@src/components/ActionButton' import { CustomIcon } from '@src/components/CustomIcon' 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 { isDesktop } from '@src/lib/isDesktop' import { openExternalBrowserIfDesktop } from '@src/lib/openWindow' @@ -15,6 +15,7 @@ import { reportRejection } from '@src/lib/trap' import { toSync } from '@src/lib/utils' import { authActor, useSettings } from '@src/lib/singletons' import { APP_VERSION, generateSignInUrl } from '@src/routes/utils' +import { withAPIBaseURL } from '@src/lib/withBaseURL' const subtleBorder = 'border border-solid border-chalkboard-30 dark:border-chalkboard-80' @@ -54,7 +55,7 @@ const SignIn = () => { const signInDesktop = async () => { // We want to invoke our command to login via device auth. const userCodeToDisplay = await window.electron - .startDeviceFlow(VITE_KC_API_BASE_URL + location.search) + .startDeviceFlow(withAPIBaseURL(location.search)) .catch(reportError) if (!userCodeToDisplay) { console.error('No user code received while trying to log in')