Skip to content

Commit

Permalink
refactor(console): refactor the code editor type definition
Browse files Browse the repository at this point in the history
refactor the code editor type definition
  • Loading branch information
simeng-li committed Mar 18, 2024
1 parent 58885b3 commit 7d971fa
Show file tree
Hide file tree
Showing 12 changed files with 192 additions and 60 deletions.
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 Button from '@/ds-components/Button';
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
2 changes: 1 addition & 1 deletion packages/console/src/pages/JwtClaims/use-jwt-customizer.ts
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 { type EditorProps } from '@monaco-editor/react';
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 @@ declare global {
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 @@ declare global {
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 @@ exports.getCustomJwtClaims = async (token, data) => {
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,20 +94,38 @@ exports.getCustomJwtClaims = async (token) => {
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`,
},
],
};

/**
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

0 comments on commit 7d971fa

Please sign in to comment.