Skip to content
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

refactor(console): refactor the code editor type definition #5516

Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 2 additions & 1 deletion packages/console/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@
"ts-node": "^10.9.2",
"tslib": "^2.4.1",
"typescript": "^5.3.3",
"zod": "^3.22.4"
"zod": "^3.22.4",
"zod-to-ts": "^1.2.0"
},
"engines": {
"node": "^20.9.0"
Expand Down
6 changes: 5 additions & 1 deletion packages/console/src/pages/JwtClaims/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ import ScriptSection from './ScriptSection';
import SettingsSection from './SettingsSection';
import * as styles from './index.module.scss';
import { type JwtClaimsFormType } from './type';
import { formatResponseDataToFormData, formatFormDataToRequestData, getApiPath } from './utils';
import {
formatResponseDataToFormData,
formatFormDataToRequestData,
getApiPath,
} from './utils/format';

type Props = {
className?: string;
Expand Down
28 changes: 20 additions & 8 deletions packages/console/src/pages/JwtClaims/MonacoCodeEditor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type Props = {
activeModelName?: string;
setActiveModel?: (name: string) => void;
value?: string;
environmentVariablesDefinition?: string;
onChange?: (value: string | undefined) => void;
onMountHandler?: (editor: IStandaloneCodeEditor) => void;
};
Expand All @@ -37,6 +38,7 @@ type Props = {
* @param {(name: string) => void} prop.setActiveModel - The callback function to set the active model. Used to switch between tabs.
* @param {string} prop.value - The value of the code editor for the current active model.
* @param {(value: string | undefined) => void} prop.onChange - The callback function to handle the value change of the code editor.
* @param {string} [prop.environmentVariablesDefinition] - The environment variables type definition for the script section.
*
* @returns
*/
Expand All @@ -46,6 +48,7 @@ function MonacoCodeEditor({
models,
activeModelName,
value,
environmentVariablesDefinition,
setActiveModel,
onChange,
onMountHandler,
Expand All @@ -71,19 +74,28 @@ function MonacoCodeEditor({

// Set the global declarations for the active model
// @see {@link https://microsoft.github.io/monaco-editor/typedoc/interfaces/languages.typescript.LanguageServiceDefaults.html#setExtraLibs}
if (activeModel.globalDeclarations) {
monaco.languages.typescript.typescriptDefaults.setExtraLibs([
{
content: activeModel.globalDeclarations,
filePath: `file:///global.d.ts`,
},
]);
if (activeModel.extraLibs) {
monaco.languages.typescript.typescriptDefaults.setExtraLibs(activeModel.extraLibs);
}
}, [activeModel, monaco]);

// Set the environment variables type definition for the active model
if (environmentVariablesDefinition) {
monaco.languages.typescript.typescriptDefaults.addExtraLib(
environmentVariablesDefinition,
'environmentVariables.d.ts'
);
}
}, [activeModel, monaco, environmentVariablesDefinition]);

const handleEditorWillMount = useCallback<BeforeMount>((monaco) => {
// Register the new logto theme
monaco.editor.defineTheme('logto-dark', logtoDarkTheme);

// Set the typescript compiler options
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
allowNonTsExtensions: true,
strictNullChecks: true,
});
}, []);

const handleEditorDidMount = useCallback<OnMount>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ export type IStandaloneThemeData = Parameters<Monaco['editor']['defineTheme']>[1

export type IStandaloneCodeEditor = Parameters<OnMount>[0];

type ExtraLibrary = {
content: string;
filePath: string;
};

export type ModelSettings = {
/** Used as the unique key for the monaco editor model @see {@link https://github.com/suren-atoyan/monaco-react?tab=readme-ov-file#multi-model-editor} */
name: string;
Expand All @@ -19,7 +24,7 @@ export type ModelSettings = {
* @see {@link https://microsoft.github.io/monaco-editor/typedoc/interfaces/languages.typescript.LanguageServiceDefaults.html#setExtraLibs}
* We use this to load the global type declarations for the active model
*/
globalDeclarations?: string;
extraLibs?: ExtraLibrary[];
};

export type ModelControl = {
Expand Down
24 changes: 21 additions & 3 deletions packages/console/src/pages/JwtClaims/ScriptSection.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
/* Code Editor for the custom JWT claims script. */
import { LogtoJwtTokenPath } from '@logto/schemas';
import { useMemo, useContext, useCallback } from 'react';
import { useFormContext, Controller } from 'react-hook-form';
import { useFormContext, Controller, useWatch } from 'react-hook-form';
import { useTranslation } from 'react-i18next';

import Card from '@/ds-components/Card';

import { CodeEditorLoadingContext } from './CodeEditorLoadingContext';
import MonacoCodeEditor, { type ModelSettings } from './MonacoCodeEditor';
import { userJwtFile, machineToMachineJwtFile } from './config';
import * as styles from './index.module.scss';
import { type JwtClaimsFormType } from './type';
import { accessTokenJwtCustomizerModel, clientCredentialsModel } from './utils/config';
import { buildEnvironmentVariablesTypeDefinition } from './utils/type-definitions';

const titlePhrases = Object.freeze({
[LogtoJwtTokenPath.AccessToken]: 'user_jwt',
Expand All @@ -21,12 +22,28 @@ function ScriptSection() {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });

const { watch, control } = useFormContext<JwtClaimsFormType>();

const tokenType = watch('tokenType');

// Need to use useWatch hook to subscribe the mutation of the environmentVariables field
// Otherwise, the default watch function's return value won't mutate when the environmentVariables field changes
const envVariables = useWatch({
control,
name: 'environmentVariables',
});

const environmentVariablesTypeDefinition = useMemo(
() => buildEnvironmentVariablesTypeDefinition(envVariables),
[envVariables]
);

const { setIsMonacoLoaded } = useContext(CodeEditorLoadingContext);

const activeModel = useMemo<ModelSettings>(
() => (tokenType === LogtoJwtTokenPath.AccessToken ? userJwtFile : machineToMachineJwtFile),
() =>
tokenType === LogtoJwtTokenPath.AccessToken
? accessTokenJwtCustomizerModel
: clientCredentialsModel,
[tokenType]
);

Expand Down Expand Up @@ -54,6 +71,7 @@ function ScriptSection() {
models={[activeModel]}
activeModelName={activeModel.name}
value={value}
environmentVariablesDefinition={environmentVariablesTypeDefinition}
onChange={(newValue) => {
// If the value is the same as the default code and the original form script value is undefined, reset the value to undefined as well
if (newValue === activeModel.defaultValue && !defaultValues?.script) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import { useTranslation } from 'react-i18next';

import Table from '@/ds-components/Table';

import { type JwtClaimsFormType } from '../type';
import {
userDataDescription,
tokenDataDescription,
fetchExternalDataEditorOptions,
fetchExternalDataCodeExample,
} from '../config';
import { type JwtClaimsFormType } from '../type';
} from '../utils/config';

import EnvironmentVariablesField from './EnvironmentVariablesField';
import GuideCard, { CardType } from './GuideCard';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
import Card from '@/ds-components/Card';

import MonacoCodeEditor, { type ModelControl } from '../MonacoCodeEditor/index.js';
import { type JwtClaimsFormType } from '../type.js';
import {
userTokenPayloadTestModel,
machineToMachineTokenPayloadTestModel,
userTokenContextTestModel,
} from '../config.js';
import { type JwtClaimsFormType } from '../type.js';
} from '../utils/config.js';

import TestResult, { type TestResultData } from './TestResult.js';
import * as styles from './index.module.scss';
Expand Down Expand Up @@ -46,7 +46,7 @@
}, [editorModels, tokenType]);

const onTestHandler = useCallback(() => {
// TODO: API integration, read form data and send the request to the server

Check warning on line 49 in packages/console/src/pages/JwtClaims/SettingsSection/TestTab.tsx

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/console/src/pages/JwtClaims/SettingsSection/TestTab.tsx#L49

[no-warning-comments] Unexpected 'todo' comment: 'TODO: API integration, read form data...'.
}, []);

const getModelControllerProps = useCallback(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import useApi from '@/hooks/use-api';
import useSwrFetcher from '@/hooks/use-swr-fetcher';
import { shouldRetryOnError } from '@/utils/request';

import { getApiPath } from './utils';
import { getApiPath } from './utils/format';

function useJwtCustomizer() {
const fetchApi = useApi({ hideErrorToast: true });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,52 +3,40 @@
import TokenFileIcon from '@/assets/icons/token-file-icon.svg';
import UserFileIcon from '@/assets/icons/user-file-icon.svg';

import type { ModelSettings } from './MonacoCodeEditor/type.js';
import type { ModelSettings } from '../MonacoCodeEditor/type.js';

import {
JwtCustomizerTypeDefinitionKey,
buildAccessTokenJwtCustomizerContextTsDefinition,
buildClientCredentialsJwtCustomizerContextTsDefinition,
} from './type-definitions.js';

/**
* JWT token code editor configuration
*/
const userJwtGlobalDeclarations = `
const accessTokenJwtCustomizerDefinition = `
declare global {
export interface CustomJwtClaims extends Record<string, any> {}

/** The user info associated with the token.
*
* @param {string} id - The user id
* @param {string} [primaryEmail] - The user email
* @param {string} [primaryPhone] - The user phone
* @param {string} [username] - The user username
* @param {string} [name] - The user name
* @param {string} [avatar] - The user avatar
*
*/
export type User = {
id: string;
primaryEmail?: string;
primaryPhone?: string;
username?: string;
name?: string;
avatar?: string;
}

/** Logto internal data that can be used to pass additional information
* @param {User} user - The user info associated with the token.
* @param {${JwtCustomizerTypeDefinitionKey.JwtCustomizerUserContext}} user - The user info associated with the token.
*/
export type Data = {
user: User;
user: ${JwtCustomizerTypeDefinitionKey.JwtCustomizerUserContext};
}

export interface Exports {
/**
* This function is called to get custom claims for the JWT token.
*
* @param {string} token -The JWT token.
* @param {${JwtCustomizerTypeDefinitionKey.AccessTokenPayload}} token -The JWT token.
* @param {Data} data - Logto internal data that can be used to pass additional information
* @param {User} data.user - The user info associated with the token.
* @param {${JwtCustomizerTypeDefinitionKey.JwtCustomizerUserContext}} data.user - The user info associated with the token.
* @param {${JwtCustomizerTypeDefinitionKey.EnvironmentVariables}} envVariables - The environment variables.
*
* @returns The custom claims.
*/
getCustomJwtClaims: (token: string, data: Data) => Promise<CustomJwtClaims>;
getCustomJwtClaims: (token: ${JwtCustomizerTypeDefinitionKey.AccessTokenPayload}, data: Data, envVariables: ${JwtCustomizerTypeDefinitionKey.EnvironmentVariables}) => Promise<CustomJwtClaims>;
}

const exports: Exports;
Expand All @@ -57,19 +45,19 @@
export { exports as default };
`;

const machineToMachineJwtGlobalDeclarations = `
const clientCredentialsJwtCustomizerDefinition = `
declare global {
export interface CustomJwtClaims extends Record<string, any> {}

export interface Exports {
/**
* This function is called to get custom claims for the JWT token.
*
* @param {string} token -The JWT token.
* @param {${JwtCustomizerTypeDefinitionKey.ClientCredentialsPayload}} token -The JWT token.
*
* @returns The custom claims.
*/
getCustomJwtClaims: (token: string) => Promise<CustomJwtClaims>;
getCustomJwtClaims: (token: ${JwtCustomizerTypeDefinitionKey.ClientCredentialsPayload}, envVariables: ${JwtCustomizerTypeDefinitionKey.EnvironmentVariables}) => Promise<CustomJwtClaims>;
}

const exports: Exports;
Expand All @@ -78,12 +66,13 @@
export { exports as default };
`;

const defaultUserJwtClaimsCode = `/**
const defaultAccessTokenJwtCustomizerCode = `/**
* This function is called to get custom claims for the JWT token.
*
* @param {string} token -The JWT token.
* @param {${JwtCustomizerTypeDefinitionKey.AccessTokenPayload}} token -The JWT token.
* @param {Data} data - Logto internal data that can be used to pass additional information
* @param {User} data.user - The user info associated with the token.
* @param {${JwtCustomizerTypeDefinitionKey.JwtCustomizerUserContext}} data.user - The user info associated with the token.
* @param {${JwtCustomizerTypeDefinitionKey.EnvironmentVariables}} [envVariables] - The environment variables.
*
* @returns The custom claims.
*/
Expand All @@ -92,10 +81,11 @@
return {};
}`;

const defaultMachineToMachineJwtClaimsCode = `/**
const defaultClientCredentialsJwtCustomizerCode = `/**
* This function is called to get custom claims for the JWT token.
*
* @param {string} token -The JWT token.
* @param {${JwtCustomizerTypeDefinitionKey.ClientCredentialsPayload}} token -The JWT token.
* @param {${JwtCustomizerTypeDefinitionKey.EnvironmentVariables}} [envVariables] - The environment variables.
*
* @returns The custom claims.
*/
Expand All @@ -104,26 +94,44 @@
return {};
}`;

export const userJwtFile: ModelSettings = {
export const accessTokenJwtCustomizerModel: ModelSettings = {
name: 'user-jwt.ts',
title: 'TypeScript',
language: 'typescript',
defaultValue: defaultUserJwtClaimsCode,
globalDeclarations: userJwtGlobalDeclarations,
defaultValue: defaultAccessTokenJwtCustomizerCode,
extraLibs: [
{
content: accessTokenJwtCustomizerDefinition,
filePath: `file:///logto-jwt-customizer.d.ts`,
},
{
content: buildAccessTokenJwtCustomizerContextTsDefinition(),
filePath: `file:///logto-jwt-customizer-context.d.ts`,
},
],
};

export const machineToMachineJwtFile: ModelSettings = {
export const clientCredentialsModel: ModelSettings = {
name: 'machine-to-machine-jwt.ts',
title: 'TypeScript',
language: 'typescript',
defaultValue: defaultMachineToMachineJwtClaimsCode,
globalDeclarations: machineToMachineJwtGlobalDeclarations,
defaultValue: defaultClientCredentialsJwtCustomizerCode,
extraLibs: [
{
content: clientCredentialsJwtCustomizerDefinition,
filePath: `file:///logto-jwt-customizer.d.ts`,
},
{
content: buildClientCredentialsJwtCustomizerContextTsDefinition(),
filePath: `file:///logto-jwt-customizer-context.d.ts`,
},
],
};

/**
* JWT claims guide card configs
*/
// TODO: align user properties and then i18n the descriptions

Check warning on line 134 in packages/console/src/pages/JwtClaims/utils/config.tsx

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/console/src/pages/JwtClaims/utils/config.tsx#L134

[no-warning-comments] Unexpected 'todo' comment: 'TODO: align user properties and then...'.
type GuideTableData = {
value: string;
description: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
type ClientCredentialsJwtCustomizer,
} from '@logto/schemas';

import type { JwtClaimsFormType } from './type';
import type { JwtClaimsFormType } from '../type';

const formatEnvVariablesResponseToFormData = (
enVariables?: AccessTokenJwtCustomizer['envVars']
Expand Down
Loading
Loading