Skip to content

Commit

Permalink
feat: Css vars (#853)
Browse files Browse the repository at this point in the history
Related to descope/etc#7890

---------

Co-authored-by: Nir Gur Arie <[email protected]>
Co-authored-by: nirgur <[email protected]>
  • Loading branch information
3 people authored Dec 2, 2024
1 parent b2b5bbe commit a49be2b
Show file tree
Hide file tree
Showing 9 changed files with 120 additions and 1 deletion.
1 change: 1 addition & 0 deletions packages/sdks/core-js-sdk/src/sdk/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ export type Options = {
samlIdpStateId?: string;
samlIdpUsername?: string;
ssoAppId?: string;
thirdPartyAppId?: string;
oidcLoginHint?: string;
abTestingKey?: number;
startOptionsVersion?: number;
Expand Down
1 change: 1 addition & 0 deletions packages/sdks/web-component/src/lib/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const SAML_IDP_STATE_ID_PARAM_NAME = 'saml_idp_state_id';
export const SAML_IDP_USERNAME_PARAM_NAME = 'saml_idp_username';
export const DESCOPE_IDP_INITIATED_PARAM_NAME = 'descope_idp_initiated';
export const SSO_APP_ID_PARAM_NAME = 'sso_app_id';
export const THIRD_PARTY_APP_ID_PARAM_NAME = 'third_party_app_id';
export const OIDC_LOGIN_HINT_PARAM_NAME = 'oidc_login_hint';
export const OIDC_PROMPT_PARAM_NAME = 'oidc_prompt';
export const OIDC_ERROR_REDIRECT_URI_PARAM_NAME = 'oidc_error_redirect_uri';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,7 @@ class BaseDescopeWc extends BaseClass {
redirectAuthBackupCallbackUri,
redirectAuthCodeChallenge,
redirectAuthInitiator,
thirdPartyAppId,
ssoQueryParams,
} = handleUrlParams();

Expand Down Expand Up @@ -537,6 +538,7 @@ class BaseDescopeWc extends BaseClass {
redirectAuthBackupCallbackUri,
redirectAuthCodeChallenge,
redirectAuthInitiator,
thirdPartyAppId,
...ssoQueryParams,
});

Expand Down
7 changes: 7 additions & 0 deletions packages/sdks/web-component/src/lib/descope-wc/DescopeWc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import { getABTestingKey } from '../helpers/abTestingKey';
import { IsChanged } from '../helpers/state';
import {
disableWebauthnButtons,
setCssVars,
setNOTPVariable,
setPhoneAutoDetectDefaultCode,
} from '../helpers/templates';
Expand Down Expand Up @@ -298,6 +299,7 @@ class DescopeWc extends BaseDescopeWc {
samlIdpResponseRelayState,
nativeResponseType,
nativePayload,
thirdPartyAppId,
...ssoQueryParams
} = currentState;

Expand Down Expand Up @@ -368,6 +370,7 @@ class DescopeWc extends BaseDescopeWc {
{
tenant,
redirectAuth,
thirdPartyAppId,
...ssoQueryParams,
client: this.client,
...(redirectUrl && { redirectUrl }),
Expand Down Expand Up @@ -616,6 +619,7 @@ class DescopeWc extends BaseDescopeWc {
flowId,
{
tenant,
thirdPartyAppId,
redirectAuth,
...ssoQueryParams,
lastAuth,
Expand Down Expand Up @@ -1014,6 +1018,9 @@ class DescopeWc extends BaseDescopeWc {

setNOTPVariable(rootElement, screenState?.notp?.image);

// set dynamic css variables that should be set at runtime
setCssVars(rootElement, clone, screenState.cssVars, this.loggerWrapper);

this.rootElement.replaceChildren(clone);

// If before html url was empty, we deduce its the first time a screen is shown
Expand Down
15 changes: 15 additions & 0 deletions packages/sdks/web-component/src/lib/helpers/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
OVERRIDE_CONTENT_URL,
OIDC_PROMPT_PARAM_NAME,
OIDC_ERROR_REDIRECT_URI_PARAM_NAME,
THIRD_PARTY_APP_ID_PARAM_NAME,
} from '../constants';
import { AutoFocusOptions, Direction, Locale, SSOQueryParams } from '../types';

Expand Down Expand Up @@ -207,10 +208,18 @@ export function getSSOAppIdParamFromUrl() {
return getUrlParam(SSO_APP_ID_PARAM_NAME);
}

export function getThirdPartyAppIdParamFromUrl() {
return getUrlParam(THIRD_PARTY_APP_ID_PARAM_NAME);
}

export function clearSSOAppIdParamFromUrl() {
resetUrlParam(SSO_APP_ID_PARAM_NAME);
}

export function clearThirdPartyAppIdParamFromUrl() {
resetUrlParam(THIRD_PARTY_APP_ID_PARAM_NAME);
}

export function getOIDCLoginHintParamFromUrl() {
return getUrlParam(OIDC_LOGIN_HINT_PARAM_NAME);
}
Expand Down Expand Up @@ -312,6 +321,11 @@ export const handleUrlParams = () => {
clearSSOAppIdParamFromUrl();
}

const thirdPartyAppId = getThirdPartyAppIdParamFromUrl();
if (thirdPartyAppId) {
clearThirdPartyAppIdParamFromUrl();
}

const oidcLoginHint = getOIDCLoginHintParamFromUrl();
if (oidcLoginHint) {
clearOIDCLoginHintParamFromUrl();
Expand Down Expand Up @@ -339,6 +353,7 @@ export const handleUrlParams = () => {
redirectAuthCallbackUrl,
redirectAuthBackupCallbackUri,
redirectAuthInitiator,
thirdPartyAppId,
ssoQueryParams: {
oidcIdpStateId,
samlIdpStateId,
Expand Down
54 changes: 53 additions & 1 deletion packages/sdks/web-component/src/lib/helpers/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
DESCOPE_ATTRIBUTE_EXCLUDE_FIELD,
HAS_DYNAMIC_VALUES_ATTR_NAME,
} from '../constants';
import { ComponentsConfig, ScreenState } from '../types';
import { ComponentsConfig, CssVars, ScreenState } from '../types';
import { shouldHandleMarkdown } from './helpers';

const ALLOWED_INPUT_CONFIG_ATTRS = ['disabled'];
Expand Down Expand Up @@ -141,6 +141,58 @@ const setFormConfigValues = (
});
};

export const setCssVars = (
rootEle: HTMLElement,
nextPageTemplate: DocumentFragment,
cssVars: CssVars,
logger: {
error: (message: string, description: string) => void;
info: (message: string, description: string) => void;
debug: (message: string, description: string) => void;
},
) => {
if (!cssVars) {
return;
}

Object.keys(cssVars).forEach((componentName) => {
if (!nextPageTemplate.querySelector(componentName)) {
logger.debug(
`Skipping css vars for component "${componentName}}"`,
`Got css vars for component ${componentName} but Could not find it on next page`,
);
}
const componentClass:
| (CustomElementConstructor & { cssVarList: CssVars })
| undefined = customElements.get(componentName) as any;

if (!componentClass) {
logger.info(
`Could not find component class for ${componentName}`,
'Check if the component is registered',
);
return;
}

Object.keys(cssVars[componentName]).forEach((cssVarKey) => {
const componentCssVars = cssVars[componentName];
const varName = componentClass?.cssVarList?.[cssVarKey];

if (!varName) {
logger.info(
`Could not find css variable name for ${cssVarKey} in ${componentName}`,
'Check if the css variable is defined in the component',
);
return;
}

const value = componentCssVars[cssVarKey];

rootEle.style.setProperty(varName, value);
});
});
};

const setElementConfig = (
baseEle: DocumentFragment,
componentsConfig: ComponentsConfig,
Expand Down
3 changes: 3 additions & 0 deletions packages/sdks/web-component/src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export type Sdk = ReturnType<typeof createSdk>;
export type SdkFlowNext = Sdk['flow']['next'];

export type ComponentsConfig = Record<string, any>;
export type CssVars = Record<string, any>;

type OmitFirstArg<F> = F extends (x: any, ...args: infer P) => infer R
? (...args: P) => R
Expand All @@ -27,6 +28,7 @@ export interface ScreenState {
errorText?: string;
errorType?: string;
componentsConfig?: ComponentsConfig;
cssVars?: CssVars;
form?: Record<string, string>;
inputs?: Record<string, string>; // Backward compatibility
lastAuth?: LastAuthState;
Expand Down Expand Up @@ -81,6 +83,7 @@ export type FlowState = {
samlIdpResponseSamlResponse: string;
samlIdpResponseRelayState: string;
nativeResponseType: string;
thirdPartyAppId: string;
nativePayload: Record<string, any>;
} & SSOQueryParams;

Expand Down
37 changes: 37 additions & 0 deletions packages/sdks/web-component/test/descope-wc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ const defaultOptionsValues = {
redirectAuth: undefined,
tenant: undefined,
locale: 'en-us',
nativeOptions: undefined,
thirdPartyAppId: null,
};

class MockFileReader {
Expand Down Expand Up @@ -4528,6 +4530,41 @@ describe('web-component', () => {
});
});

describe('cssVars', () => {
it('should set css vars on root element', async () => {
const spyGet = jest.spyOn(customElements, 'get');
spyGet.mockReturnValue({ cssVarList: { varName: '--var-name' } } as any);

startMock.mockReturnValueOnce(
generateSdkResponse({
screenState: {
cssVars: { 'descope-button': { varName: 'value' } },
},
}),
);

pageContent = `<descope-button>click</descope-button><div>Loaded</div><input class="descope-input" name="customComponent">`;

document.body.innerHTML = `<h1>Custom element test</h1> <descope-wc flow-id="otpSignInEmail" project-id="1"></descope-wc>`;

await waitFor(() => screen.getByShadowText('Loaded'), {
timeout: WAIT_TIMEOUT,
});

const shadowEle =
document.getElementsByTagName('descope-wc')[0].shadowRoot;
const rootEle = shadowEle.querySelector('#root');

await waitFor(
() =>
expect(rootEle).toHaveStyle({
'--var-name': 'value',
}),
{ timeout: WAIT_TIMEOUT },
);
});
});

describe('Input Flows', () => {
it('should pre-populate input with flat structure config structure', async () => {
startMock.mockReturnValueOnce(generateSdkResponse());
Expand Down
1 change: 1 addition & 0 deletions packages/sdks/web-js-sdk/src/sdk/flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type Options = Pick<
| 'samlIdpStateId'
| 'samlIdpUsername'
| 'ssoAppId'
| 'thirdPartyAppId'
| 'oidcLoginHint'
| 'preview'
| 'abTestingKey'
Expand Down

0 comments on commit a49be2b

Please sign in to comment.