Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion spx-gui/.env
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
# set in the corresponding `.env.[mode]` files. However, for the ones that
# require default values, such as feature flags, they are set here.

# App xbuilder config.

# Base URL for the spx-backend API.
#
# Required.
Expand Down Expand Up @@ -85,7 +87,22 @@ VITE_ACCOUNT_API_PROXY_TARGET=""
# accordingly.
VITE_ACCOUNT_WEB_ORIGIN=""

# Account OAuth configuration for the main-site hosted sign-in flow.
# Account OAuth configuration used by app xbuilder for the main-site hosted
# sign-in flow.
#
# Required when enabling the new Account OAuth flow.
VITE_ACCOUNT_OAUTH_CLIENT_ID=""

# App account config.
#
# These settings are intentionally separate from the app xbuilder settings
# above. App account should not inherit app xbuilder Sentry, language, or feature
# configuration just because both apps are built from this package.

# Sentry configuration for app account.
VITE_ACCOUNT_SENTRY_DSN=""
VITE_ACCOUNT_SENTRY_TRACES_SAMPLE_RATE="0.8"
VITE_ACCOUNT_SENTRY_LSP_SAMPLE_RATE="0.1"

# Default language for app account, e.g. `en`, `zh`.
VITE_ACCOUNT_DEFAULT_LANG="en"
17 changes: 11 additions & 6 deletions spx-gui/src/apis/account/oauth.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Client } from '@/apis/common/client'
import { apiBaseUrl } from '@/utils/env'
import type { OAuthAPIs } from '@/utils/oauth'
import { accountClient } from './common'

Expand Down Expand Up @@ -85,11 +84,17 @@ class AccountOAuthApis implements OAuthAPIs {
}

/** Account OAuth APIs for use by app xbuilder. */
export const accountOAuthApisForXBuilder = new AccountOAuthApis(
new Client({
baseUrl: apiBaseUrl + '/account'
})
)
const accountOAuthClientForXBuilder = new Client({})

export const accountOAuthApisForXBuilder = new AccountOAuthApis(accountOAuthClientForXBuilder)

export type AccountOAuthAPIsForXBuilderConfig = {
apiBaseUrl: string
}

export function configureAccountOAuthAPIsForXBuilder(config: AccountOAuthAPIsForXBuilderConfig) {
accountOAuthClientForXBuilder.setBaseUrl(config.apiBaseUrl + '/account')
}

/** Account OAuth APIs for use by app account. */
export const accountOAuthApis = new AccountOAuthApis(accountClient)
23 changes: 16 additions & 7 deletions spx-gui/src/apis/common/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,13 @@ export type JSONSSEEvent = {
}

export type ClientOptions = {
baseUrl: string
baseUrl?: string
fetchFn?: typeof fetch
}

export class Client {
constructor(options: ClientOptions) {
this.baseUrl = options.baseUrl
this.baseUrl = options.baseUrl ?? null
this.fetchFn = options.fetchFn ?? globalThis.fetch.bind(globalThis)
}

Expand All @@ -80,13 +80,22 @@ export class Client {
this.tokenProvider = provider
}

private baseUrl: string
setBaseUrl(baseUrl: string) {
this.baseUrl = baseUrl
}

private baseUrl: string | null
private fetchFn: typeof fetch
private defaultTimeout = 10 * 1000 // 10 seconds

private getBaseUrl() {
if (this.baseUrl == null) throw new Error('API client base URL is not set')
return this.baseUrl
}

/** Get full URL for a given API path */
urlFor(path: string) {
const concated = this.baseUrl + path
const concated = this.getBaseUrl() + path
return new URL(concated, window.location.origin)
}

Expand All @@ -103,7 +112,7 @@ export class Client {
const traceData = Sentry.getTraceData()
const sentryTraceHeader = traceData['sentry-trace']
const sentryBaggageHeader = traceData['baggage']
const url = this.baseUrl + path
const url = this.getBaseUrl() + path
const method = options?.method ?? 'GET'
const body = payload != null ? JSON.stringify(payload) : null
const headers = options?.headers ?? new Headers()
Expand All @@ -119,7 +128,7 @@ export class Client {
const traceData = Sentry.getTraceData()
const sentryTraceHeader = traceData['sentry-trace']
const sentryBaggageHeader = traceData['baggage']
const url = this.baseUrl + path
const url = this.getBaseUrl() + path
const method = options?.method ?? 'POST'
const body = new URLSearchParams()
Object.entries(payload).forEach(([key, value]) => {
Expand Down Expand Up @@ -178,7 +187,7 @@ export class Client {
const traceData = Sentry.getTraceData()
const sentryTraceHeader = traceData['sentry-trace']
const sentryBaggageHeader = traceData['baggage']
const url = this.baseUrl + path
const url = this.getBaseUrl() + path
const method = options?.method ?? 'GET'
const headers = options?.headers ?? new Headers()
await this.injectAuthorization(headers, options?.signal)
Expand Down
17 changes: 10 additions & 7 deletions spx-gui/src/apis/common/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { apiBaseUrl } from '@/utils/env'
import { Client } from './client'

export type PaginationParams = {
Expand Down Expand Up @@ -39,13 +38,17 @@ export function timeStringify(time: number) {

/**
* The default client instance for app XBuilder to make requests to spx-backend APIs.
* Requests made through this client will have the base URL set to `apiBaseUrl` from environment variables.
* The token provider is expected to be set separately on app initialization (see details in setup.ts)
* so credentials will be included in requests.
* The base URL and token provider are expected to be configured on app initialization.
*/
export const client = new Client({
baseUrl: apiBaseUrl
})
export const client = new Client({})

export type CommonAPIConfig = {
apiBaseUrl: string
}

export function configureCommonAPIs(config: CommonAPIConfig) {
client.setBaseUrl(config.apiBaseUrl)
}

/** Art style indicates the visual style or aesthetic approach used in the creation of graphics */
export const enum ArtStyle {
Expand Down
18 changes: 17 additions & 1 deletion spx-gui/src/apis/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
* @desc File-related APIs of spx-backend
*/

import { usercontentBaseUrl, usercontentBucket } from '@/utils/env'
import { client, type UniversalUrl, type UniversalToWebUrlMap } from './common'
import { UniversalUrlScheme, parseUniversalUrl } from '@/utils/universal-url'

Expand All @@ -19,6 +18,22 @@ export type UploadSession = {
region: string
}

export type FileAPIConfig = {
usercontentBaseUrl: string
usercontentBucket: string
}

let fileAPIConfig: FileAPIConfig | null = null

export function configureFileAPIs(config: FileAPIConfig) {
fileAPIConfig = config
}

function getFileAPIConfig() {
if (fileAPIConfig == null) throw new Error('File API config is not set')
return fileAPIConfig
}

export function createUploadSession() {
return client.post('/upload-sessions') as Promise<UploadSession>
}
Expand All @@ -33,6 +48,7 @@ export async function createFileURLSignatures(objects: UniversalUrl[]): Promise<

/** Workaround for https://github.com/goplus/builder/issues/1598 */
function workAroundIssue1598(objects: UniversalUrl[]): UniversalToWebUrlMap {
const { usercontentBaseUrl, usercontentBucket } = getFileAPIConfig()
return objects.reduce((map, universalUrl) => {
const parsed = parseUniversalUrl(universalUrl)
if (parsed.scheme === UniversalUrlScheme.Kodo) {
Expand Down
19 changes: 19 additions & 0 deletions spx-gui/src/apps/account/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { SentryConfig } from '@/setup/sentry'

export type AccountEnv = {
defaultLang: string
sentry: SentryConfig
}

function parseSampleRate(value: string, fallback: number) {
return parseFloat(value) || fallback
}
Comment on lines +8 to +10

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The parseSampleRate function uses parseFloat(value) || fallback. If the sample rate is explicitly configured as 0 (which is a valid setting to disable tracing/sampling), parseFloat(value) evaluates to 0, causing the function to incorrectly fall back to the default value (0.1).

Use isNaN to check if the parsed value is valid instead.

Suggested change
function parseSampleRate(value: string, fallback: number) {
return parseFloat(value) || fallback
}
function parseSampleRate(value: string, fallback: number) {
const parsed = parseFloat(value)
return isNaN(parsed) ? fallback : parsed
}


export const accountEnv: AccountEnv = {
defaultLang: (import.meta.env.VITE_ACCOUNT_DEFAULT_LANG as string) || 'en',
sentry: {
dsn: (import.meta.env.VITE_ACCOUNT_SENTRY_DSN as string) || '',
tracesSampleRate: parseSampleRate(import.meta.env.VITE_ACCOUNT_SENTRY_TRACES_SAMPLE_RATE as string, 0.1),
lspSampleRate: parseSampleRate(import.meta.env.VITE_ACCOUNT_SENTRY_LSP_SAMPLE_RATE as string, 0.1)
}
}
5 changes: 3 additions & 2 deletions spx-gui/src/apps/account/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { initDayjs } from '@/setup/dayjs'
import { initI18n } from '@/setup/i18n'
import { initSentry } from '@/setup/sentry'

import { accountEnv } from './env'
import App from './App.vue'
import router from './router'

Expand All @@ -15,7 +16,7 @@ import router from './router'
initDayjs()

const app = createApp(App)
initSentry(app, router)
initI18n(app)
initSentry(app, router, accountEnv.sentry)
initI18n(app, accountEnv)
app.use(router)
app.mount('#app')
12 changes: 12 additions & 0 deletions spx-gui/src/apps/xbuilder/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { XBuilderEnv } from './env'

let xbuilderConfig: XBuilderEnv | null = null

export function configureXBuilder(config: XBuilderEnv) {
xbuilderConfig = config
}

export function getXBuilderConfig() {
if (xbuilderConfig == null) throw new Error('XBuilder config is not set')
return xbuilderConfig
}
35 changes: 35 additions & 0 deletions spx-gui/src/apps/xbuilder/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { SentryConfig } from '@/setup/sentry'

export type XBuilderEnv = {
apiBaseUrl: string
usercontentBaseUrl: string
usercontentBucket: string
disableAIGC: boolean
spxVersion: string
showLicense: boolean
showTutorialsEntry: boolean
defaultLang: string
accountOAuthClientId: string
sentry: SentryConfig
}

function parseSampleRate(value: string, fallback: number) {
return parseFloat(value) || fallback
}
Comment on lines +16 to +18

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The parseSampleRate function uses parseFloat(value) || fallback. If the sample rate is explicitly configured as 0 (which is a valid setting to disable tracing/sampling), parseFloat(value) evaluates to 0, causing the function to incorrectly fall back to the default value (0.1).

Use isNaN to check if the parsed value is valid instead.

Suggested change
function parseSampleRate(value: string, fallback: number) {
return parseFloat(value) || fallback
}
function parseSampleRate(value: string, fallback: number) {
const parsed = parseFloat(value)
return isNaN(parsed) ? fallback : parsed
}


export const xbuilderEnv: XBuilderEnv = {
apiBaseUrl: import.meta.env.VITE_API_BASE_URL as string,
usercontentBaseUrl: import.meta.env.VITE_USERCONTENT_BASE_URL as string,
usercontentBucket: import.meta.env.VITE_USERCONTENT_BUCKET as string,
disableAIGC: import.meta.env.VITE_DISABLE_AIGC === 'true',
spxVersion: import.meta.env.VITE_SPX_VERSION as string,
showLicense: import.meta.env.VITE_SHOW_LICENSE === 'true',
showTutorialsEntry: import.meta.env.VITE_SHOW_TUTORIALS_ENTRY === 'true',
defaultLang: (import.meta.env.VITE_DEFAULT_LANG as string) || 'en',
accountOAuthClientId: import.meta.env.VITE_ACCOUNT_OAUTH_CLIENT_ID as string,
sentry: {
dsn: (import.meta.env.VITE_SENTRY_DSN as string) || '',
tracesSampleRate: parseSampleRate(import.meta.env.VITE_SENTRY_TRACES_SAMPLE_RATE as string, 0.1),
lspSampleRate: parseSampleRate(import.meta.env.VITE_SENTRY_LSP_SAMPLE_RATE as string, 0.1)
}
}
5 changes: 3 additions & 2 deletions spx-gui/src/apps/xbuilder/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import '@/polyfills'
import '@/app.css'
import { createApp } from 'vue'
import { setup, configureApp } from '@/setup'
import { xbuilderEnv } from './env'
import { initRouter } from './router'
import App from './App.vue'

setup()
setup(xbuilderEnv)
const app = createApp(App)
const router = initRouter(app)
configureApp(app, router)
configureApp(app, router, xbuilderEnv)
app.mount('#app')
4 changes: 2 additions & 2 deletions spx-gui/src/apps/xbuilder/pages/docs/api.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ApiReference } from '@scalar/api-reference'
import type { AnyApiReferenceConfiguration } from '@scalar/types/api-reference'
import '@scalar/api-reference/style.css'
import apiDocument from '@docs/openapi.yaml?raw'
import { apiBaseUrl } from '@/utils/env'
import { getXBuilderConfig } from '@/apps/xbuilder/config'
import { usePageTitle } from '@/utils/utils'

usePageTitle([
Expand All @@ -25,7 +25,7 @@ const configuration = reactive<AnyApiReferenceConfiguration>({
},
servers: [
{
url: apiBaseUrl
url: getXBuilderConfig().apiBaseUrl
}
],
hideClientButton: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@
import { computed, ref, shallowReactive, shallowRef, watch } from 'vue'
import { stripExt } from '@/utils/path'
import type { LocaleMessage } from '@/utils/i18n'
import { disableAIGC } from '@/utils/env'
import { getXBuilderConfig } from '@/apps/xbuilder/config'
import { Costume } from '@/models/spx/costume'
import { File } from '@/models/common/file'
import { UIButton, UIFormModal } from '@/components/ui'
Expand All @@ -124,6 +124,7 @@ import { useMessageHandle } from '@/utils/exception'
import { useNetwork } from '@/utils/network'

const { isOnline } = useNetwork()
const { disableAIGC } = getXBuilderConfig()

const props = withDefaults(
defineProps<{
Expand Down
3 changes: 2 additions & 1 deletion spx-gui/src/components/community/CommunityNavbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,12 @@ import NavbarNewProjectItem from '@/components/navbar/NavbarNewProjectItem.vue'
import NavbarOpenProjectItem from '@/components/navbar/NavbarOpenProjectItem.vue'
import { searchKeywordQueryParamName } from '@/apps/xbuilder/pages/community/search.vue'
import { getSearchRoute } from '@/apps/xbuilder/router'
import { showTutorialsEntry } from '@/utils/env'
import { getXBuilderConfig } from '@/apps/xbuilder/config'
import NavbarLang from '../navbar/NavbarLang.vue'
import NavbarTutorials from '../navbar/NavbarTutorials.vue'
import { isSignedIn } from '@/stores/user'

const { showTutorialsEntry } = getXBuilderConfig()
const router = useRouter()
const searchInput = ref('')

Expand Down
4 changes: 3 additions & 1 deletion spx-gui/src/components/community/footer/CommunityFooter.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<script setup lang="ts">
import { showLicense } from '@/utils/env'
import { getXBuilderConfig } from '@/apps/xbuilder/config'
import { UILink } from '@/components/ui'

const { showLicense } = getXBuilderConfig()
</script>

<template>
Expand Down
4 changes: 3 additions & 1 deletion spx-gui/src/components/editor/navbar/EditorNavbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ import { type SpxProject } from '@/models/spx/project'
import { useSignedInUser } from '@/stores/user'
import { Visibility } from '@/apis/common'
import { getProjectPageRoute } from '@/apps/xbuilder/router'
import { showTutorialsEntry } from '@/utils/env'
import { getXBuilderConfig } from '@/apps/xbuilder/config'
import { useModifyProjectName, usePublishProject, useRemoveProject, useUnpublishProject } from '@/components/project'
import { useLoadFromScratchModal } from '@/components/asset'
import { xbpHelpers } from '@/models/common/xbp'
Expand All @@ -178,6 +178,8 @@ import EditorAutoSaveStateIcon from './EditorAutoSaveStateIcon.vue'
import EditorProjectDisplayName from './EditorProjectDisplayName.vue'
import EditorCheckoutReleaseButton from './EditorCheckoutReleaseButton.vue'
import { EditMode, type EditorState } from '../editor-state'

const { showTutorialsEntry } = getXBuilderConfig()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The variable declaration const { showTutorialsEntry } = getXBuilderConfig() is placed in the middle of the import statements block. All import statements must precede any other code execution or variable declarations at the module level to adhere to standard style guidelines (such as ESLint's import/first rule) and prevent potential hoisting or initialization issues.

Please move this statement below all the import statements (e.g., right before const props = defineProps(...)).

import { isDeveloperMode } from '@/utils/developer-mode'
import importProjectSvg from './icons/import-project.svg'
import exportProjectSvg from './icons/export-project.svg'
Expand Down
Loading
Loading