diff --git a/app/gui2/src/appRunner.ts b/app/gui2/src/appRunner.ts index 1a74dc2bfc..e38a818544 100644 --- a/app/gui2/src/appRunner.ts +++ b/app/gui2/src/appRunner.ts @@ -11,6 +11,14 @@ async function runApp( _metadata?: object | undefined, pinia?: Pinia | undefined, ) { + const ignoreParamsRegex = (() => { + if (_metadata) + if ('ignoreParamsRegex' in _metadata) + if (_metadata['ignoreParamsRegex'] instanceof RegExp) return _metadata['ignoreParamsRegex'] + + return null + })() + running = true const { mountProjectApp } = await import('./createApp') if (!running) return @@ -19,7 +27,11 @@ async function runApp( function onUnrecognizedOption(path: string[]) { unrecognizedOptions.push(path.join('.')) } - const intermediateConfig = mergeConfig(baseConfig, urlParams(), { onUnrecognizedOption }) + const intermediateConfig = mergeConfig( + baseConfig, + urlParams({ ignoreKeysRegExp: ignoreParamsRegex }), + { onUnrecognizedOption }, + ) const appConfig = mergeConfig(intermediateConfig, config ?? {}) unmount = await mountProjectApp({ config: appConfig, accessToken, unrecognizedOptions }, pinia) } diff --git a/app/gui2/src/util/urlParams.ts b/app/gui2/src/util/urlParams.ts index 2458254168..d9f2492b4f 100644 --- a/app/gui2/src/util/urlParams.ts +++ b/app/gui2/src/util/urlParams.ts @@ -1,13 +1,25 @@ import type { StringConfig } from './config' +export interface UrlParamsProps { + ignoreKeysRegExp?: RegExp | null +} + /** Returns the parameters passed in the URL query string. */ -export function urlParams(): StringConfig { +export function urlParams(props: UrlParamsProps = {}): StringConfig { + const { ignoreKeysRegExp } = props + const params: StringConfig = {} const urlParams = new URLSearchParams(window.location.search) + for (const [name, value] of urlParams.entries()) { let obj = params const path = name.split('.') const lastSegment = path.pop() + + if (ignoreKeysRegExp != null && ignoreKeysRegExp.test(name)) { + continue + } + if (lastSegment == null) { console.error(`Invalid URL parameter name: '${name}'`) } else { diff --git a/app/ide-desktop/lib/dashboard/src/appUtils.tsx b/app/ide-desktop/lib/dashboard/src/appUtils.tsx index 216bb34bd7..6845116d2c 100644 --- a/app/ide-desktop/lib/dashboard/src/appUtils.tsx +++ b/app/ide-desktop/lib/dashboard/src/appUtils.tsx @@ -4,6 +4,10 @@ // === Constants === // ================= +// ============= +// === Paths === +// ============= + /** Path to the root of the app (i.e., the Cloud dashboard). */ export const DASHBOARD_PATH = '/' /** Path to the login page. */ @@ -28,3 +32,9 @@ export const ALL_PATHS_REGEX = new RegExp( `${FORGOT_PASSWORD_PATH}|${RESET_PASSWORD_PATH}|${SET_USERNAME_PATH}|` + `${ENTER_OFFLINE_MODE_PATH}|${SUBSCRIBE_PATH})$` ) + +// =========== +// === URL === +// =========== + +export const SEARCH_PARAMS_PREFIX = 'cloud-ide_' diff --git a/app/ide-desktop/lib/dashboard/src/hooks/searchParamsStateHooks.ts b/app/ide-desktop/lib/dashboard/src/hooks/searchParamsStateHooks.ts index a0567cb944..1cd4795584 100644 --- a/app/ide-desktop/lib/dashboard/src/hooks/searchParamsStateHooks.ts +++ b/app/ide-desktop/lib/dashboard/src/hooks/searchParamsStateHooks.ts @@ -7,6 +7,8 @@ import * as React from 'react' import * as reactRouterDom from 'react-router-dom' +import * as appUtils from '#/appUtils' + import * as eventCallback from '#/hooks/eventCallbackHooks' import * as lazyMemo from '#/hooks/useLazyMemoHooks' @@ -32,22 +34,31 @@ export function useSearchParamsState( ): SearchParamsStateReturnType { const [searchParams, setSearchParams] = reactRouterDom.useSearchParams() + const prefixedKey = `${appUtils.SEARCH_PARAMS_PREFIX}${key}` + const lazyDefaultValueInitializer = lazyMemo.useLazyMemoHooks(defaultValue, []) const predicateEventCallback = eventCallback.useEventCallback(predicate) const clear = eventCallback.useEventCallback((replace: boolean = false) => { - searchParams.delete(key) + searchParams.delete(prefixedKey) setSearchParams(searchParams, { replace }) }) + const unprefixedValue = searchParams.get(key) + if (unprefixedValue != null) { + searchParams.set(prefixedKey, unprefixedValue) + searchParams.delete(key) + setSearchParams(searchParams) + } + const rawValue = React.useMemo(() => { - const maybeValue = searchParams.get(key) + const maybeValue = searchParams.get(prefixedKey) const defaultValueFrom = lazyDefaultValueInitializer() return maybeValue != null ? safeJsonParse.safeJsonParse(maybeValue, defaultValueFrom, (unknown): unknown is T => true) : defaultValueFrom - }, [key, lazyDefaultValueInitializer, searchParams]) + }, [prefixedKey, lazyDefaultValueInitializer, searchParams]) const isValueValid = predicateEventCallback(rawValue) @@ -71,7 +82,7 @@ export function useSearchParamsState( if (nextValue === lazyDefaultValueInitializer()) { clear() } else { - searchParams.set(key, JSON.stringify(nextValue)) + searchParams.set(prefixedKey, JSON.stringify(nextValue)) setSearchParams(searchParams) } }) diff --git a/app/ide-desktop/lib/dashboard/src/layouts/Editor.tsx b/app/ide-desktop/lib/dashboard/src/layouts/Editor.tsx index 9e03e8a1f4..cfb7b9b0a2 100644 --- a/app/ide-desktop/lib/dashboard/src/layouts/Editor.tsx +++ b/app/ide-desktop/lib/dashboard/src/layouts/Editor.tsx @@ -1,6 +1,8 @@ /** @file The container that launches the IDE. */ import * as React from 'react' +import * as appUtils from '#/appUtils' + import * as toastAndLogHooks from '#/hooks/toastAndLogHooks' import * as backendModule from '#/services/Backend' @@ -114,7 +116,10 @@ export default function Editor(props: EditorProps) { }, }, accessToken, - { projectId: project.projectId } + { + projectId: project.projectId, + ignoreParamsRegex: new RegExp(`^${appUtils.SEARCH_PARAMS_PREFIX}(.+)$`), + } ) } catch (error) { toastAndLog('openEditorError', error)