From cf1164d4234c87fb98713ef8fe10a2c3d60525b2 Mon Sep 17 00:00:00 2001 From: Jacek Date: Thu, 13 Mar 2025 11:40:51 -0500 Subject: [PATCH 01/23] feat(clerk-js): allow initialization with defaults --- .changeset/dirty-lizards-count.md | 5 + integration/tests/reverification.test.ts | 2 +- packages/clerk-js/bundlewatch.config.json | 4 +- packages/clerk-js/src/core/clerk.ts | 27 +-- .../clerk-js/src/core/resources/AuthConfig.ts | 20 +- .../src/core/resources/CommerceSettings.ts | 6 +- .../src/core/resources/DisplayConfig.ts | 194 +++++++++--------- .../src/core/resources/Environment.ts | 61 +++--- .../core/resources/OrganizationSettings.ts | 47 +++-- .../src/core/resources/UserSettings.ts | 154 +++++++------- .../resources/__tests__/Environment.test.ts | 6 + .../resources/__tests__/UserSettings.test.ts | 4 + .../__snapshots__/Environment.test.ts.snap | 121 +++++++++-- .../__snapshots__/UserSettings.test.ts.snap | 18 ++ .../src/ui/components/SignIn/SignInStart.tsx | 6 +- .../components/SignUp/SignUpVerifyEmail.tsx | 8 +- .../ui/components/SignUp/signUpFormHelpers.ts | 49 ++--- .../ui/components/UserProfile/AccountPage.tsx | 8 +- .../ui/components/UserProfile/EmailForm.tsx | 2 +- .../UserProfile/MfaPhoneCodeScreen.tsx | 2 +- .../ui/components/UserProfile/ProfileForm.tsx | 8 +- .../components/UserProfile/SecurityPage.tsx | 2 +- .../components/UserProfile/UsernameForm.tsx | 2 +- .../src/ui/components/UserProfile/utils.ts | 4 +- 24 files changed, 446 insertions(+), 314 deletions(-) create mode 100644 .changeset/dirty-lizards-count.md create mode 100644 packages/clerk-js/src/core/resources/__tests__/__snapshots__/UserSettings.test.ts.snap diff --git a/.changeset/dirty-lizards-count.md b/.changeset/dirty-lizards-count.md new file mode 100644 index 00000000000..fa310cd89bb --- /dev/null +++ b/.changeset/dirty-lizards-count.md @@ -0,0 +1,5 @@ +--- +'@clerk/clerk-js': minor +--- + +Allow initialization of Clerk.environment even if the API call to get environment details fails diff --git a/integration/tests/reverification.test.ts b/integration/tests/reverification.test.ts index f6b61814643..3df2f61313c 100644 --- a/integration/tests/reverification.test.ts +++ b/integration/tests/reverification.test.ts @@ -160,7 +160,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withReverification] })( fictionalEmail: true, withPhoneNumber: true, }); - const bapiFakeUser = await u.services.users.createBapiUser({ + await u.services.users.createBapiUser({ ...delFakeUser, username: undefined, phoneNumber: undefined, diff --git a/packages/clerk-js/bundlewatch.config.json b/packages/clerk-js/bundlewatch.config.json index d5015e80235..558d49e1eba 100644 --- a/packages/clerk-js/bundlewatch.config.json +++ b/packages/clerk-js/bundlewatch.config.json @@ -2,7 +2,7 @@ "files": [ { "path": "./dist/clerk.js", "maxSize": "572kB" }, { "path": "./dist/clerk.browser.js", "maxSize": "78kB" }, - { "path": "./dist/clerk.headless.js", "maxSize": "50KB" }, + { "path": "./dist/clerk.headless.js", "maxSize": "50.5KB" }, { "path": "./dist/ui-common*.js", "maxSize": "92KB" }, { "path": "./dist/vendors*.js", "maxSize": "26.5KB" }, { "path": "./dist/coinbase*.js", "maxSize": "35.5KB" }, @@ -12,7 +12,7 @@ { "path": "./dist/organizationswitcher*.js", "maxSize": "5KB" }, { "path": "./dist/organizationlist*.js", "maxSize": "5.5KB" }, { "path": "./dist/signin*.js", "maxSize": "12.4KB" }, - { "path": "./dist/signup*.js", "maxSize": "6.5KB" }, + { "path": "./dist/signup*.js", "maxSize": "6.55KB" }, { "path": "./dist/userbutton*.js", "maxSize": "5KB" }, { "path": "./dist/userprofile*.js", "maxSize": "15KB" }, { "path": "./dist/userverification*.js", "maxSize": "5KB" }, diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 32f0fb7ece7..8effc91eb6d 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -325,9 +325,6 @@ export class Clerk implements ClerkInterface { public constructor(key: string, options?: DomainOrProxyUrl) { key = (key || '').trim(); - this.#domain = options?.domain; - this.#proxyUrl = options?.proxyUrl; - if (!key) { return errorThrower.throwMissingPublishableKeyError(); } @@ -338,8 +335,11 @@ export class Clerk implements ClerkInterface { return errorThrower.throwInvalidPublishableKeyError({ key }); } - this.#publishableKey = key; + this.#domain = options?.domain; + this.#proxyUrl = options?.proxyUrl; + this.environment = Environment.getInstance(); this.#instanceType = publishableKey.instanceType; + this.#publishableKey = key; this.#fapiClient = createFapiClient({ domain: this.domain, @@ -2052,10 +2052,9 @@ export class Clerk implements ClerkInterface { this.updateEnvironment(res); }); - const initClient = () => { - return Client.getOrCreateInstance() - .fetch() - .then(res => this.updateClient(res)); + const initClient = async () => { + const res = await Client.getOrCreateInstance().fetch(); + this.updateClient(res); }; const initComponents = () => { @@ -2068,16 +2067,20 @@ export class Clerk implements ClerkInterface { } }; - await Promise.all([initEnvironmentPromise, initClient()]).catch(async e => { - // limit the changes for this specific error for now + const [envResult, clientResult] = await Promise.allSettled([initEnvironmentPromise, initClient()]); + if (clientResult.status === 'rejected') { + const e = clientResult.reason; + if (isClerkAPIResponseError(e) && e.errors[0].code === 'requires_captcha') { - await initEnvironmentPromise; + if (envResult.status === 'rejected') { + await initEnvironmentPromise.catch(() => {}); // Ignore failure + } initComponents(); await initClient(); } else { throw e; } - }); + } this.#authService?.setClientUatCookieForDevelopmentInstances(); diff --git a/packages/clerk-js/src/core/resources/AuthConfig.ts b/packages/clerk-js/src/core/resources/AuthConfig.ts index 7de2c33b628..d6b9332ed69 100644 --- a/packages/clerk-js/src/core/resources/AuthConfig.ts +++ b/packages/clerk-js/src/core/resources/AuthConfig.ts @@ -4,29 +4,33 @@ import { unixEpochToDate } from '../../utils/date'; import { BaseResource } from './internal'; export class AuthConfig extends BaseResource implements AuthConfigResource { - singleSessionMode!: boolean; claimedAt: Date | null = null; - reverification!: boolean; + reverification: boolean = false; + singleSessionMode: boolean = false; - public constructor(data: AuthConfigJSON) { + public constructor(data: AuthConfigJSON | null = null) { super(); + this.fromJSON(data); } protected fromJSON(data: AuthConfigJSON | null): this { - this.singleSessionMode = data ? data.single_session_mode : true; - this.claimedAt = data?.claimed_at ? unixEpochToDate(data.claimed_at) : null; - this.reverification = data ? data.reverification : true; + if (!data) { + return this; + } + this.claimedAt = data.claimed_at ? unixEpochToDate(data.claimed_at) : null; + this.reverification = data.reverification ?? false; + this.singleSessionMode = data.single_session_mode ?? false; return this; } public __internal_toSnapshot(): AuthConfigJSONSnapshot { return { object: 'auth_config', - id: this.id || '', - single_session_mode: this.singleSessionMode, claimed_at: this.claimedAt ? this.claimedAt.getTime() : null, + id: this.id ?? '', reverification: this.reverification, + single_session_mode: this.singleSessionMode, }; } } diff --git a/packages/clerk-js/src/core/resources/CommerceSettings.ts b/packages/clerk-js/src/core/resources/CommerceSettings.ts index 5c0e0db1928..3bee52e021f 100644 --- a/packages/clerk-js/src/core/resources/CommerceSettings.ts +++ b/packages/clerk-js/src/core/resources/CommerceSettings.ts @@ -10,9 +10,11 @@ import { BaseResource } from './internal'; * @internal */ export class __experimental_CommerceSettings extends BaseResource implements __experimental_CommerceSettingsResource { - stripePublishableKey!: string; + stripePublishableKey: string = ''; - public constructor(data: __experimental_CommerceSettingsJSON | __experimental_CommerceSettingsJSONSnapshot) { + public constructor( + data: __experimental_CommerceSettingsJSON | __experimental_CommerceSettingsJSONSnapshot | null = null, + ) { super(); this.fromJSON(data); } diff --git a/packages/clerk-js/src/core/resources/DisplayConfig.ts b/packages/clerk-js/src/core/resources/DisplayConfig.ts index c9fb68c4a89..4ceb0055f60 100644 --- a/packages/clerk-js/src/core/resources/DisplayConfig.ts +++ b/packages/clerk-js/src/core/resources/DisplayConfig.ts @@ -12,48 +12,49 @@ import type { import { BaseResource } from './internal'; export class DisplayConfig extends BaseResource implements DisplayConfigResource { - id!: string; - afterSignInUrl!: string; - afterSignOutAllUrl!: string; - afterSignOutOneUrl!: string; - afterSignOutUrl!: string; - afterSignUpUrl!: string; - afterSwitchSessionUrl!: string; - applicationName!: string; - backendHost!: string; - branded!: boolean; - captchaPublicKey: string | null = null; - captchaWidgetType: CaptchaWidgetType = null; + afterCreateOrganizationUrl: string = ''; + afterJoinWaitlistUrl: string = ''; + afterLeaveOrganizationUrl: string = ''; + afterSignInUrl: string = ''; + afterSignOutAllUrl: string = ''; + afterSignOutOneUrl: string = ''; + afterSignOutUrl: string = ''; + afterSignUpUrl: string = ''; + afterSwitchSessionUrl: string = ''; + applicationName: string = ''; + backendHost: string = ''; + branded: boolean = false; + captchaHeartbeat: boolean = false; + captchaHeartbeatIntervalMs?: number; + captchaOauthBypass: OAuthStrategy[] = []; captchaProvider: CaptchaProvider = 'turnstile'; + captchaPublicKey: string | null = null; captchaPublicKeyInvisible: string | null = null; - captchaOauthBypass: OAuthStrategy[] = []; - captchaHeartbeat: boolean = false; - captchaHeartbeatIntervalMs?: number = undefined; - homeUrl!: string; - instanceEnvironmentType!: string; - faviconImageUrl!: string; - logoImageUrl!: string; - preferredSignInStrategy!: PreferredSignInStrategy; - signInUrl!: string; - signUpUrl!: string; - supportEmail!: string; - theme!: DisplayThemeJSON; - userProfileUrl!: string; + captchaWidgetType: CaptchaWidgetType = null; clerkJSVersion?: string; + createOrganizationUrl: string = ''; experimental__forceOauthFirst?: boolean; - organizationProfileUrl!: string; - createOrganizationUrl!: string; - afterLeaveOrganizationUrl!: string; - afterCreateOrganizationUrl!: string; + faviconImageUrl: string = ''; googleOneTapClientId?: string; - showDevModeWarning!: boolean; - termsUrl!: string; - privacyPolicyUrl!: string; - waitlistUrl!: string; - afterJoinWaitlistUrl!: string; + homeUrl: string = ''; + id: string = ''; + instanceEnvironmentType: string = ''; + logoImageUrl: string = ''; + organizationProfileUrl: string = ''; + preferredSignInStrategy: PreferredSignInStrategy = 'password'; + privacyPolicyUrl: string = ''; + showDevModeWarning: boolean = false; + signInUrl: string = ''; + signUpUrl: string = ''; + supportEmail: string = ''; + termsUrl: string = ''; + theme: DisplayThemeJSON = {} as DisplayThemeJSON; + userProfileUrl: string = ''; + waitlistUrl: string = ''; - public constructor(data: DisplayConfigJSON | DisplayConfigJSONSnapshot) { + public constructor(data: DisplayConfigJSON | DisplayConfigJSONSnapshot | null = null) { super(); + this.fromJSON(data); } @@ -62,86 +63,85 @@ export class DisplayConfig extends BaseResource implements DisplayConfigResource return this; } - this.id = data.id; - this.instanceEnvironmentType = data.instance_environment_type; - this.applicationName = data.application_name; - this.theme = data.theme; - this.preferredSignInStrategy = data.preferred_sign_in_strategy; - this.logoImageUrl = data.logo_image_url; - this.faviconImageUrl = data.favicon_image_url; - this.homeUrl = data.home_url; - this.signInUrl = data.sign_in_url; - this.signUpUrl = data.sign_up_url; - this.userProfileUrl = data.user_profile_url; - this.afterSignInUrl = data.after_sign_in_url; - this.afterSignUpUrl = data.after_sign_up_url; - this.afterSignOutOneUrl = data.after_sign_out_one_url; - this.afterSignOutAllUrl = data.after_sign_out_all_url; - this.afterSwitchSessionUrl = data.after_switch_session_url; - this.branded = data.branded; - this.captchaPublicKey = data.captcha_public_key; - this.captchaWidgetType = data.captcha_widget_type; - this.captchaProvider = data.captcha_provider; - this.captchaPublicKeyInvisible = data.captcha_public_key_invisible; - // These are the OAuth strategies we used to bypass the captcha for by default - // before the introduction of the captcha_oauth_bypass field - this.captchaOauthBypass = data.captcha_oauth_bypass || ['oauth_google', 'oauth_microsoft', 'oauth_apple']; - this.captchaHeartbeat = data.captcha_heartbeat || false; - this.captchaHeartbeatIntervalMs = data.captcha_heartbeat_interval_ms; - this.supportEmail = data.support_email || ''; - this.clerkJSVersion = data.clerk_js_version; - this.organizationProfileUrl = data.organization_profile_url; - this.createOrganizationUrl = data.create_organization_url; - this.afterLeaveOrganizationUrl = data.after_leave_organization_url; - this.afterCreateOrganizationUrl = data.after_create_organization_url; - this.googleOneTapClientId = data.google_one_tap_client_id; - this.showDevModeWarning = data.show_devmode_warning; - this.termsUrl = data.terms_url; - this.privacyPolicyUrl = data.privacy_policy_url; - this.waitlistUrl = data.waitlist_url; - this.afterJoinWaitlistUrl = data.after_join_waitlist_url; + this.afterCreateOrganizationUrl = data.after_create_organization_url ?? this.afterCreateOrganizationUrl; + this.afterJoinWaitlistUrl = data.after_join_waitlist_url ?? this.afterJoinWaitlistUrl; + this.afterLeaveOrganizationUrl = data.after_leave_organization_url ?? this.afterLeaveOrganizationUrl; + this.afterSignInUrl = data.after_sign_in_url ?? this.afterSignInUrl; + this.afterSignOutAllUrl = data.after_sign_out_all_url ?? this.afterSignOutAllUrl; + this.afterSignOutOneUrl = data.after_sign_out_one_url ?? this.afterSignOutOneUrl; + this.afterSignUpUrl = data.after_sign_up_url ?? this.afterSignUpUrl; + this.afterSwitchSessionUrl = data.after_switch_session_url ?? this.afterSwitchSessionUrl; + this.applicationName = data.application_name ?? this.applicationName; + this.branded = data.branded ?? this.branded; + this.captchaHeartbeat = data.captcha_heartbeat ?? this.captchaHeartbeat; + this.captchaHeartbeatIntervalMs = data.captcha_heartbeat_interval_ms ?? this.captchaHeartbeatIntervalMs; + this.captchaOauthBypass = data.captcha_oauth_bypass ?? this.captchaOauthBypass; + this.captchaProvider = data.captcha_provider ?? this.captchaProvider; + this.captchaPublicKey = data.captcha_public_key ?? this.captchaPublicKey; + this.captchaPublicKeyInvisible = data.captcha_public_key_invisible ?? this.captchaPublicKeyInvisible; + this.captchaWidgetType = data.captcha_widget_type ?? this.captchaWidgetType; + this.clerkJSVersion = data.clerk_js_version ?? this.clerkJSVersion; + this.createOrganizationUrl = data.create_organization_url ?? this.createOrganizationUrl; + this.faviconImageUrl = data.favicon_image_url ?? this.faviconImageUrl; + this.googleOneTapClientId = data.google_one_tap_client_id ?? this.googleOneTapClientId; + this.homeUrl = data.home_url ?? this.homeUrl; + this.id = data.id ?? this.id; + this.instanceEnvironmentType = data.instance_environment_type ?? this.instanceEnvironmentType; + this.logoImageUrl = data.logo_image_url ?? this.logoImageUrl; + this.organizationProfileUrl = data.organization_profile_url ?? this.organizationProfileUrl; + this.preferredSignInStrategy = data.preferred_sign_in_strategy ?? this.preferredSignInStrategy; + this.privacyPolicyUrl = data.privacy_policy_url ?? this.privacyPolicyUrl; + this.showDevModeWarning = data.show_devmode_warning ?? this.showDevModeWarning; + this.signInUrl = data.sign_in_url ?? this.signInUrl; + this.signUpUrl = data.sign_up_url ?? this.signUpUrl; + this.supportEmail = data.support_email ?? this.supportEmail; + this.termsUrl = data.terms_url ?? this.termsUrl; + this.theme = data.theme ?? this.theme; + this.userProfileUrl = data.user_profile_url ?? this.userProfileUrl; + this.waitlistUrl = data.waitlist_url ?? this.waitlistUrl; + return this; } public __internal_toSnapshot(): DisplayConfigJSONSnapshot { return { object: 'display_config', - id: this.id, - instance_environment_type: this.instanceEnvironmentType, - application_name: this.applicationName, - theme: this.theme, - preferred_sign_in_strategy: this.preferredSignInStrategy, - logo_image_url: this.logoImageUrl, - favicon_image_url: this.faviconImageUrl, - home_url: this.homeUrl, - sign_in_url: this.signInUrl, - sign_up_url: this.signUpUrl, - user_profile_url: this.userProfileUrl, + after_create_organization_url: this.afterCreateOrganizationUrl, + after_join_waitlist_url: this.afterJoinWaitlistUrl, + after_leave_organization_url: this.afterLeaveOrganizationUrl, after_sign_in_url: this.afterSignInUrl, - after_sign_up_url: this.afterSignUpUrl, - after_sign_out_one_url: this.afterSignOutOneUrl, after_sign_out_all_url: this.afterSignOutAllUrl, + after_sign_out_one_url: this.afterSignOutOneUrl, + after_sign_up_url: this.afterSignUpUrl, after_switch_session_url: this.afterSwitchSessionUrl, + application_name: this.applicationName, branded: this.branded, - captcha_public_key: this.captchaPublicKey, - captcha_widget_type: this.captchaWidgetType, + captcha_heartbeat_interval_ms: this.captchaHeartbeatIntervalMs, + captcha_heartbeat: this.captchaHeartbeat, + captcha_oauth_bypass: this.captchaOauthBypass, captcha_provider: this.captchaProvider, captcha_public_key_invisible: this.captchaPublicKeyInvisible, - captcha_oauth_bypass: this.captchaOauthBypass, - captcha_heartbeat: this.captchaHeartbeat, - captcha_heartbeat_interval_ms: this.captchaHeartbeatIntervalMs, - support_email: this.supportEmail, + captcha_public_key: this.captchaPublicKey, + captcha_widget_type: this.captchaWidgetType, clerk_js_version: this.clerkJSVersion, - organization_profile_url: this.organizationProfileUrl, create_organization_url: this.createOrganizationUrl, - after_leave_organization_url: this.afterLeaveOrganizationUrl, - after_create_organization_url: this.afterCreateOrganizationUrl, + favicon_image_url: this.faviconImageUrl, google_one_tap_client_id: this.googleOneTapClientId, + home_url: this.homeUrl, + id: this.id, + instance_environment_type: this.instanceEnvironmentType, + logo_image_url: this.logoImageUrl, + organization_profile_url: this.organizationProfileUrl, + preferred_sign_in_strategy: this.preferredSignInStrategy, + privacy_policy_url: this.privacyPolicyUrl, show_devmode_warning: this.showDevModeWarning, + sign_in_url: this.signInUrl, + sign_up_url: this.signUpUrl, + support_email: this.supportEmail, terms_url: this.termsUrl, - privacy_policy_url: this.privacyPolicyUrl, + theme: this.theme, + user_profile_url: this.userProfileUrl, waitlist_url: this.waitlistUrl, - after_join_waitlist_url: this.afterJoinWaitlistUrl, }; } } diff --git a/packages/clerk-js/src/core/resources/Environment.ts b/packages/clerk-js/src/core/resources/Environment.ts index ae033e5ecb1..aa352539dc0 100644 --- a/packages/clerk-js/src/core/resources/Environment.ts +++ b/packages/clerk-js/src/core/resources/Environment.ts @@ -15,13 +15,14 @@ import { OrganizationSettings } from './OrganizationSettings'; export class Environment extends BaseResource implements EnvironmentResource { private static instance: Environment; + authConfig: AuthConfigResource = new AuthConfig(); + displayConfig: DisplayConfigResource = new DisplayConfig(); + maintenanceMode: boolean = false; pathRoot = '/environment'; - authConfig!: AuthConfigResource; - displayConfig!: DisplayConfigResource; - userSettings!: UserSettingsResource; - organizationSettings!: OrganizationSettingsResource; - __experimental_commerceSettings!: __experimental_CommerceSettingsResource; - maintenanceMode!: boolean; + // @ts-expect-error - This is a partial object, but we want to ensure that all attributes are present. + userSettings: UserSettingsResource = new UserSettings(); + organizationSettings: OrganizationSettingsResource = new OrganizationSettings(); + __experimental_commerceSettings: __experimental_CommerceSettingsResource = new __experimental_CommerceSettings(); public static getInstance(): Environment { if (!Environment.instance) { @@ -33,54 +34,56 @@ export class Environment extends BaseResource implements EnvironmentResource { constructor(data: EnvironmentJSON | EnvironmentJSONSnapshot | null = null) { super(); + this.fromJSON(data); } - fetch({ touch, fetchMaxTries }: { touch: boolean; fetchMaxTries?: number } = { touch: false }): Promise { - if (touch) { - return this._basePatch({}); + protected fromJSON(data: EnvironmentJSONSnapshot | EnvironmentJSON | null): this { + if (!data) { + return this; } - return this._baseGet({ fetchMaxTries }); + + this.authConfig = new AuthConfig(data.auth_config); + this.displayConfig = new DisplayConfig(data.display_config); + this.maintenanceMode = data.maintenance_mode ?? this.maintenanceMode; + this.organizationSettings = new OrganizationSettings(data.organization_settings); + // @ts-expect-error - This is a partial object, but we want to ensure that all attributes are present. + this.userSettings = new UserSettings(data.user_settings); + this.__experimental_commerceSettings = new __experimental_CommerceSettings(data.commerce_settings); + + return this; } - isSingleSession = (): boolean => { - return this.authConfig.singleSessionMode; + fetch({ touch, fetchMaxTries }: { touch: boolean; fetchMaxTries?: number } = { touch: false }): Promise { + return touch ? this._basePatch({}) : this._baseGet({ fetchMaxTries }); + } + + isDevelopmentOrStaging = (): boolean => { + return !this.isProduction(); }; isProduction = (): boolean => { return this.displayConfig.instanceEnvironmentType === 'production'; }; - isDevelopmentOrStaging = (): boolean => { - return !this.isProduction(); + isSingleSession = (): boolean => { + return this.authConfig.singleSessionMode; }; onWindowLocationHost = (): boolean => { return this.displayConfig.backendHost === window.location.host; }; - protected fromJSON(data: EnvironmentJSONSnapshot | EnvironmentJSON | null): this { - if (data) { - this.authConfig = new AuthConfig(data.auth_config); - this.__experimental_commerceSettings = new __experimental_CommerceSettings(data.commerce_settings); - this.displayConfig = new DisplayConfig(data.display_config); - this.userSettings = new UserSettings(data.user_settings); - this.organizationSettings = new OrganizationSettings(data.organization_settings); - this.maintenanceMode = data.maintenance_mode; - } - return this; - } - public __internal_toSnapshot(): EnvironmentJSONSnapshot { return { object: 'environment', - id: this.id || '', auth_config: this.authConfig.__internal_toSnapshot(), display_config: this.displayConfig.__internal_toSnapshot(), - user_settings: this.userSettings.__internal_toSnapshot(), + id: this.id || '', + maintenance_mode: this.maintenanceMode, organization_settings: this.organizationSettings.__internal_toSnapshot(), + user_settings: this.userSettings.__internal_toSnapshot(), commerce_settings: this.__experimental_commerceSettings.__internal_toSnapshot(), - maintenance_mode: this.maintenanceMode, }; } } diff --git a/packages/clerk-js/src/core/resources/OrganizationSettings.ts b/packages/clerk-js/src/core/resources/OrganizationSettings.ts index 183f8e55b45..97436045f02 100644 --- a/packages/clerk-js/src/core/resources/OrganizationSettings.ts +++ b/packages/clerk-js/src/core/resources/OrganizationSettings.ts @@ -8,39 +8,48 @@ import type { import { BaseResource } from './internal'; export class OrganizationSettings extends BaseResource implements OrganizationSettingsResource { - enabled!: boolean; - maxAllowedMemberships!: number; - actions!: { - adminDelete: boolean; - }; - domains!: { + actions: { adminDelete: boolean } = { adminDelete: false }; + domains: { enabled: boolean; enrollmentModes: OrganizationEnrollmentMode[]; defaultRole: string | null; + } = { + enabled: false, + enrollmentModes: [], + defaultRole: null, }; + enabled: boolean = false; + maxAllowedMemberships: number = 1; - public constructor(data: OrganizationSettingsJSON | OrganizationSettingsJSONSnapshot) { + public constructor(data: OrganizationSettingsJSON | OrganizationSettingsJSONSnapshot | null = null) { super(); + this.fromJSON(data); } protected fromJSON(data: OrganizationSettingsJSON | OrganizationSettingsJSONSnapshot | null): this { - const { enabled = false, max_allowed_memberships = 0, actions, domains } = data || {}; - this.enabled = enabled; - this.maxAllowedMemberships = max_allowed_memberships; - this.actions = { adminDelete: actions?.admin_delete || false }; - this.domains = { - enabled: domains?.enabled || false, - enrollmentModes: domains?.enrollment_modes || [], - defaultRole: domains?.default_role || null, - }; + if (!data) { + return this; + } + + if (data.actions) { + this.actions.adminDelete = data.actions.admin_delete ?? this.actions.adminDelete; + } + + if (data.domains) { + this.domains.enabled = data.domains.enabled ?? this.domains.enabled; + this.domains.enrollmentModes = data.domains.enrollment_modes ?? this.domains.enrollmentModes; + this.domains.defaultRole = data.domains.default_role ?? this.domains.defaultRole; + } + + this.enabled = data.enabled ?? this.enabled; + this.maxAllowedMemberships = data.max_allowed_memberships ?? this.maxAllowedMemberships; + return this; } public __internal_toSnapshot(): OrganizationSettingsJSONSnapshot { return { - enabled: this.enabled, - max_allowed_memberships: this.maxAllowedMemberships, actions: { admin_delete: this.actions.adminDelete, }, @@ -49,6 +58,8 @@ export class OrganizationSettings extends BaseResource implements OrganizationSe enrollment_modes: this.domains.enrollmentModes, default_role: this.domains.defaultRole, }, + enabled: this.enabled, + max_allowed_memberships: this.maxAllowedMemberships, } as unknown as OrganizationSettingsJSONSnapshot; } } diff --git a/packages/clerk-js/src/core/resources/UserSettings.ts b/packages/clerk-js/src/core/resources/UserSettings.ts index 15f9abb9c3e..37d127d0ef2 100644 --- a/packages/clerk-js/src/core/resources/UserSettings.ts +++ b/packages/clerk-js/src/core/resources/UserSettings.ts @@ -33,73 +33,114 @@ export type Actions = { */ export class UserSettings extends BaseResource implements UserSettingsResource { id = undefined; - social!: OAuthProviders; - saml!: SamlSettings; - enterpriseSSO!: EnterpriseSSOSettings; + // @ts-expect-error - This is a partial object, but we want to ensure that all actions are present. + actions: Partial = {}; + // @ts-expect-error - This is a partial object, but we want to ensure that all attributes are present. + attributes: Partial = {}; + enterpriseSSO: EnterpriseSSOSettings = {} as EnterpriseSSOSettings; + passkeySettings: PasskeySettingsData = {} as PasskeySettingsData; + passwordSettings: PasswordSettingsData = {} as PasswordSettingsData; + saml: SamlSettings = {} as SamlSettings; + signIn: SignInData = {} as SignInData; + signUp: SignUpData = {} as SignUpData; + social: OAuthProviders = {} as OAuthProviders; + usernameSettings: UsernameSettingsData = {} as UsernameSettingsData; + + get authenticatableSocialStrategies(): OAuthStrategy[] { + if (!this.social) { + return []; + } + + return Object.entries(this.social) + .filter(([, desc]) => desc.enabled && desc.authenticatable) + .map(([, desc]) => desc.strategy) + .sort(); + } + + get enabledFirstFactorIdentifiers(): Array { + if (!this.attributes) { + return []; + } + + return Object.entries(this.attributes) + .filter(([name, attr]) => attr.used_for_first_factor && !name.startsWith('web3')) + .map(([name]) => name) as Array; + } + + get socialProviderStrategies(): OAuthStrategy[] { + if (!this.social) { + return []; + } + + return Object.entries(this.social) + .filter(([, desc]) => desc.enabled) + .map(([, desc]) => desc.strategy) + .sort(); + } - attributes!: Attributes; - actions!: Actions; - signIn!: SignInData; - signUp!: SignUpData; - passwordSettings!: PasswordSettingsData; - passkeySettings!: PasskeySettingsData; - usernameSettings!: UsernameSettingsData; + get web3FirstFactors(): Web3Strategy[] { + if (!this.attributes) { + return []; + } - socialProviderStrategies: OAuthStrategy[] = []; - authenticatableSocialStrategies: OAuthStrategy[] = []; - web3FirstFactors: Web3Strategy[] = []; - enabledFirstFactorIdentifiers: Array = []; + return Object.entries(this.attributes) + .filter(([name, attr]) => attr.used_for_first_factor && name.startsWith('web3')) + .map(([, desc]) => desc.first_factors) + .flat() as any as Web3Strategy[]; + } - public constructor(data: UserSettingsJSON | UserSettingsJSONSnapshot) { + /** + * Constructor now accepts an optional data object. + */ + public constructor(data: UserSettingsJSON | UserSettingsJSONSnapshot | null = null) { super(); this.fromJSON(data); } get instanceIsPasswordBased() { - return this.attributes.password.enabled && this.attributes.password.required; + return Boolean(this.attributes?.password?.enabled && this.attributes.password?.required); } get hasValidAuthFactor() { - return ( - this.attributes.email_address.enabled || - this.attributes.phone_number.enabled || - (this.attributes.password.required && this.attributes.username.required) + return Boolean( + this.attributes?.email_address?.enabled || + this.attributes?.phone_number?.enabled || + (this.attributes.password?.required && this.attributes.username?.required), ); } + /** + * fromJSON now safely returns the instance even if null is provided. + */ protected fromJSON(data: UserSettingsJSON | UserSettingsJSONSnapshot | null): this { if (!data) { return this; } - this.social = data.social; - this.saml = data.saml; - this.enterpriseSSO = data.enterprise_sso; this.attributes = Object.fromEntries( Object.entries(data.attributes).map(a => [a[0], { ...a[1], name: a[0] }]), ) as Attributes; this.actions = data.actions; - this.signIn = data.sign_in; - this.signUp = data.sign_up; + this.enterpriseSSO = data.enterprise_sso; + this.passkeySettings = data.passkey_settings; this.passwordSettings = { ...data.password_settings, - min_length: Math.max(data?.password_settings?.min_length, defaultMinPasswordLength), + min_length: Math.max(data.password_settings?.min_length ?? defaultMinPasswordLength, defaultMinPasswordLength), max_length: - data?.password_settings?.max_length === 0 + data.password_settings?.max_length === 0 ? defaultMaxPasswordLength - : Math.min(data?.password_settings?.max_length, defaultMaxPasswordLength), + : Math.min(data.password_settings?.max_length ?? defaultMaxPasswordLength, defaultMaxPasswordLength), }; + this.saml = data.saml; + this.signIn = data.sign_in; + this.signUp = data.sign_up; + this.social = data.social; this.usernameSettings = { ...data.username_settings, - min_length: Math.max(data?.username_settings?.min_length, defaultMinUsernameLength), - max_length: Math.min(data?.username_settings?.max_length, defaultMaxUsernameLength), + min_length: Math.max(data.username_settings?.min_length ?? defaultMinUsernameLength, defaultMinUsernameLength), + max_length: Math.min(data.username_settings?.max_length ?? defaultMaxUsernameLength, defaultMaxUsernameLength), }; - this.passkeySettings = data.passkey_settings; - this.socialProviderStrategies = this.getSocialProviderStrategies(data.social); - this.authenticatableSocialStrategies = this.getAuthenticatableSocialStrategies(data.social); - this.web3FirstFactors = this.getWeb3FirstFactors(this.attributes); - this.enabledFirstFactorIdentifiers = this.getEnabledFirstFactorIdentifiers(this.attributes); return this; } @@ -116,47 +157,4 @@ export class UserSettings extends BaseResource implements UserSettingsResource { passkey_settings: this.passkeySettings, } as unknown as UserSettingsJSONSnapshot; } - - private getEnabledFirstFactorIdentifiers(attributes: Attributes): Array { - if (!attributes) { - return []; - } - - return Object.entries(attributes) - .filter(([name, attr]) => attr.used_for_first_factor && !name.startsWith('web3')) - .map(([name]) => name) as Array; - } - - private getWeb3FirstFactors(attributes: Attributes): Web3Strategy[] { - if (!attributes) { - return []; - } - - return Object.entries(attributes) - .filter(([name, attr]) => attr.used_for_first_factor && name.startsWith('web3')) - .map(([, desc]) => desc.first_factors) - .flat() as any as Web3Strategy[]; - } - - private getSocialProviderStrategies(social: OAuthProviders): OAuthStrategy[] { - if (!social) { - return []; - } - - return Object.entries(social) - .filter(([, desc]) => desc.enabled) - .map(([, desc]) => desc.strategy) - .sort(); - } - - private getAuthenticatableSocialStrategies(social: OAuthProviders): OAuthStrategy[] { - if (!social) { - return []; - } - - return Object.entries(social) - .filter(([, desc]) => desc.enabled && desc.authenticatable) - .map(([, desc]) => desc.strategy) - .sort(); - } } diff --git a/packages/clerk-js/src/core/resources/__tests__/Environment.test.ts b/packages/clerk-js/src/core/resources/__tests__/Environment.test.ts index 813fb21be21..e1f996a7d04 100644 --- a/packages/clerk-js/src/core/resources/__tests__/Environment.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/Environment.test.ts @@ -3,6 +3,12 @@ import type { EnvironmentJSONSnapshot } from '@clerk/types'; import { Environment } from '../internal'; describe('Environment', () => { + it('defaults values when instantiated without arguments', () => { + const environment = new Environment(); + + expect(environment).toMatchSnapshot(); + }); + it('has the same initial properties', () => { const environmentJSON = { object: 'environment', diff --git a/packages/clerk-js/src/core/resources/__tests__/UserSettings.test.ts b/packages/clerk-js/src/core/resources/__tests__/UserSettings.test.ts index c8460fcf411..94ae59e76bf 100644 --- a/packages/clerk-js/src/core/resources/__tests__/UserSettings.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/UserSettings.test.ts @@ -3,6 +3,10 @@ import type { UserSettingsJSON } from '@clerk/types'; import { UserSettings } from '../internal'; describe('UserSettings', () => { + it('defaults values when instantiated with no arguments', function () { + expect(new UserSettings()).toMatchSnapshot(); + }); + it('returns enabled web3 first factors', function () { const sut = new UserSettings({ attributes: { diff --git a/packages/clerk-js/src/core/resources/__tests__/__snapshots__/Environment.test.ts.snap b/packages/clerk-js/src/core/resources/__tests__/__snapshots__/Environment.test.ts.snap index bee8b3b9ac2..dfabe474126 100644 --- a/packages/clerk-js/src/core/resources/__tests__/__snapshots__/Environment.test.ts.snap +++ b/packages/clerk-js/src/core/resources/__tests__/__snapshots__/Environment.test.ts.snap @@ -10,7 +10,7 @@ exports[`Environment __internal_toSnapshot() 1`] = ` "single_session_mode": true, }, "commerce_settings": { - "stripe_publishable_key": undefined, + "stripe_publishable_key": "", }, "display_config": { "after_create_organization_url": "", @@ -26,14 +26,14 @@ exports[`Environment __internal_toSnapshot() 1`] = ` "captcha_heartbeat": false, "captcha_heartbeat_interval_ms": undefined, "captcha_oauth_bypass": [], - "captcha_provider": null, + "captcha_provider": "turnstile", "captcha_public_key": null, "captcha_public_key_invisible": null, "captcha_widget_type": null, "clerk_js_version": "5", "create_organization_url": "", "favicon_image_url": "", - "google_one_tap_client_id": null, + "google_one_tap_client_id": undefined, "home_url": "", "id": "display_config_DUMMY_ID", "instance_environment_type": "development", @@ -41,12 +41,12 @@ exports[`Environment __internal_toSnapshot() 1`] = ` "object": "display_config", "organization_profile_url": "", "preferred_sign_in_strategy": "password", - "privacy_policy_url": null, + "privacy_policy_url": "", "show_devmode_warning": true, "sign_in_url": "", "sign_up_url": "", "support_email": "", - "terms_url": null, + "terms_url": "", "theme": { "accounts": { "background_color": "#ffffff", @@ -268,10 +268,97 @@ exports[`Environment __internal_toSnapshot() 1`] = ` } `; +exports[`Environment defaults values when instantiated without arguments 1`] = ` +Environment { + "__experimental_commerceSettings": __experimental_CommerceSettings { + "pathRoot": "", + "stripePublishableKey": "", + }, + "authConfig": AuthConfig { + "claimedAt": null, + "pathRoot": "", + "reverification": false, + "singleSessionMode": false, + }, + "displayConfig": DisplayConfig { + "afterCreateOrganizationUrl": "", + "afterJoinWaitlistUrl": "", + "afterLeaveOrganizationUrl": "", + "afterSignInUrl": "", + "afterSignOutAllUrl": "", + "afterSignOutOneUrl": "", + "afterSignOutUrl": "", + "afterSignUpUrl": "", + "afterSwitchSessionUrl": "", + "applicationName": "", + "backendHost": "", + "branded": false, + "captchaHeartbeat": false, + "captchaOauthBypass": [], + "captchaProvider": "turnstile", + "captchaPublicKey": null, + "captchaPublicKeyInvisible": null, + "captchaWidgetType": null, + "createOrganizationUrl": "", + "faviconImageUrl": "", + "homeUrl": "", + "id": "", + "instanceEnvironmentType": "", + "logoImageUrl": "", + "organizationProfileUrl": "", + "pathRoot": "", + "preferredSignInStrategy": "password", + "privacyPolicyUrl": "", + "showDevModeWarning": false, + "signInUrl": "", + "signUpUrl": "", + "supportEmail": "", + "termsUrl": "", + "theme": {}, + "userProfileUrl": "", + "waitlistUrl": "", + }, + "isDevelopmentOrStaging": [Function], + "isProduction": [Function], + "isSingleSession": [Function], + "maintenanceMode": false, + "onWindowLocationHost": [Function], + "organizationSettings": OrganizationSettings { + "actions": { + "adminDelete": false, + }, + "domains": { + "defaultRole": null, + "enabled": false, + "enrollmentModes": [], + }, + "enabled": false, + "maxAllowedMemberships": 1, + "pathRoot": "", + }, + "pathRoot": "/environment", + "userSettings": UserSettings { + "actions": {}, + "attributes": {}, + "enterpriseSSO": {}, + "id": undefined, + "passkeySettings": {}, + "passwordSettings": {}, + "pathRoot": "", + "saml": {}, + "signIn": {}, + "signUp": {}, + "social": {}, + "usernameSettings": {}, + }, +} +`; + exports[`Environment has the same initial properties 1`] = ` Environment { "__experimental_commerceSettings": __experimental_CommerceSettings { "pathRoot": "", + "stripePublishableKey": "", }, "authConfig": AuthConfig { "claimedAt": null, @@ -286,21 +373,23 @@ Environment { "afterSignInUrl": "", "afterSignOutAllUrl": "", "afterSignOutOneUrl": "", + "afterSignOutUrl": "", "afterSignUpUrl": "", "afterSwitchSessionUrl": "", "applicationName": "", + "backendHost": "", "branded": true, "captchaHeartbeat": false, "captchaHeartbeatIntervalMs": undefined, "captchaOauthBypass": [], - "captchaProvider": null, + "captchaProvider": "turnstile", "captchaPublicKey": null, "captchaPublicKeyInvisible": null, "captchaWidgetType": null, "clerkJSVersion": "5", "createOrganizationUrl": "", "faviconImageUrl": "", - "googleOneTapClientId": null, + "googleOneTapClientId": undefined, "homeUrl": "", "id": "display_config_DUMMY_ID", "instanceEnvironmentType": "development", @@ -308,12 +397,12 @@ Environment { "organizationProfileUrl": "", "pathRoot": "", "preferredSignInStrategy": "password", - "privacyPolicyUrl": null, + "privacyPolicyUrl": "", "showDevModeWarning": true, "signInUrl": "", "signUpUrl": "", "supportEmail": "", - "termsUrl": null, + "termsUrl": "", "theme": { "accounts": { "background_color": "#ffffff", @@ -489,12 +578,6 @@ Environment { "verify_at_sign_up": false, }, }, - "authenticatableSocialStrategies": [ - "oauth_google", - ], - "enabledFirstFactorIdentifiers": [ - "email_address", - ], "enterpriseSSO": undefined, "id": undefined, "passkeySettings": { @@ -544,14 +627,10 @@ Environment { "strategy": "oauth_google", }, }, - "socialProviderStrategies": [ - "oauth_google", - ], "usernameSettings": { - "max_length": NaN, - "min_length": NaN, + "max_length": 64, + "min_length": 4, }, - "web3FirstFactors": [], }, } `; diff --git a/packages/clerk-js/src/core/resources/__tests__/__snapshots__/UserSettings.test.ts.snap b/packages/clerk-js/src/core/resources/__tests__/__snapshots__/UserSettings.test.ts.snap new file mode 100644 index 00000000000..0616b18a080 --- /dev/null +++ b/packages/clerk-js/src/core/resources/__tests__/__snapshots__/UserSettings.test.ts.snap @@ -0,0 +1,18 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`UserSettings defaults values when instantiated with no arguments 1`] = ` +UserSettings { + "actions": {}, + "attributes": {}, + "enterpriseSSO": {}, + "id": undefined, + "passkeySettings": {}, + "passwordSettings": {}, + "pathRoot": "", + "saml": {}, + "signIn": {}, + "signUp": {}, + "social": {}, + "usernameSettings": {}, +} +`; diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx index 1d3100156c3..bd538b1f196 100644 --- a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx @@ -49,7 +49,7 @@ const useAutoFillPasskey = () => { await authenticateWithPasskey({ flow: 'autofill' }); } - if (passkeySettings.allow_autofill && attributes.passkey.enabled) { + if (passkeySettings.allow_autofill && attributes.passkey?.enabled) { runAutofillPasskey(); } }, []); @@ -395,7 +395,7 @@ function SignInStartInternal(): JSX.Element { signUpMode: userSettings.signUp.mode, redirectUrl, redirectUrlComplete, - passwordEnabled: userSettings.attributes.password.required, + passwordEnabled: userSettings.attributes.password?.required ?? false, }); } else { handleError(e, [identifierField, instantPasswordField], card.setError); @@ -483,7 +483,7 @@ function SignInStartInternal(): JSX.Element { ) : null} - {userSettings.attributes.passkey.enabled && + {userSettings.attributes.passkey?.enabled && userSettings.passkeySettings.show_sign_in_button && isWebSupported && ( diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpVerifyEmail.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpVerifyEmail.tsx index 4057763d3e0..fced8e73920 100644 --- a/packages/clerk-js/src/ui/components/SignUp/SignUpVerifyEmail.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/SignUpVerifyEmail.tsx @@ -6,11 +6,7 @@ import { SignUpEmailLinkCard } from './SignUpEmailLinkCard'; export const SignUpVerifyEmail = withCardStateProvider(() => { const { userSettings } = useEnvironment(); const { attributes } = userSettings; - const emailLinkStrategyEnabled = attributes.email_address.verifications.includes('email_link'); + const emailLinkStrategyEnabled = attributes.email_address?.verifications?.includes('email_link'); - if (emailLinkStrategyEnabled) { - return ; - } - - return ; + return emailLinkStrategyEnabled ? : ; }); diff --git a/packages/clerk-js/src/ui/components/SignUp/signUpFormHelpers.ts b/packages/clerk-js/src/ui/components/SignUp/signUpFormHelpers.ts index a2f9ae591c9..4e83dbaa513 100644 --- a/packages/clerk-js/src/ui/components/SignUp/signUpFormHelpers.ts +++ b/packages/clerk-js/src/ui/components/SignUp/signUpFormHelpers.ts @@ -37,7 +37,7 @@ export type Fields = { }; type FieldDeterminationProps = { - attributes: Attributes; + attributes: Partial; activeCommIdentifierType?: ActiveIdentifier; hasTicket?: boolean; hasEmail?: boolean; @@ -103,7 +103,10 @@ export function minimizeFieldsForExistingSignup(fields: Fields, signUp: SignUpRe } } -export const getInitialActiveIdentifier = (attributes: Attributes, isProgressiveSignUp: boolean): ActiveIdentifier => { +export const getInitialActiveIdentifier = ( + attributes: Partial, + isProgressiveSignUp: boolean, +): ActiveIdentifier => { if (emailOrPhone(attributes, isProgressiveSignUp)) { // If we are in the case of Email OR Phone, email takes priority return 'emailAddress'; @@ -111,11 +114,11 @@ export const getInitialActiveIdentifier = (attributes: Attributes, isProgressive const { email_address, phone_number } = attributes; - if (email_address.enabled && isProgressiveSignUp ? email_address.required : email_address.used_for_first_factor) { + if (email_address?.enabled && isProgressiveSignUp ? email_address.required : email_address?.used_for_first_factor) { return 'emailAddress'; } - if (phone_number.enabled && isProgressiveSignUp ? phone_number.required : phone_number.used_for_first_factor) { + if (phone_number?.enabled && isProgressiveSignUp ? phone_number.required : phone_number?.used_for_first_factor) { return 'phoneNumber'; } @@ -128,14 +131,14 @@ export function showFormFields(userSettings: UserSettingsResource): boolean { return userSettings.hasValidAuthFactor || (!authenticatableSocialStrategies.length && !web3FirstFactors.length); } -export function emailOrPhone(attributes: Attributes, isProgressiveSignUp: boolean) { +export function emailOrPhone(attributes: Partial, isProgressiveSignUp: boolean) { const { email_address, phone_number } = attributes; - if (isProgressiveSignUp) { - return email_address.enabled && phone_number.enabled && !email_address.required && !phone_number.required; - } - - return email_address.used_for_first_factor && phone_number.used_for_first_factor; + return Boolean( + isProgressiveSignUp + ? email_address?.enabled && phone_number?.enabled && !email_address.required && !phone_number.required + : email_address?.used_for_first_factor && phone_number?.used_for_first_factor, + ); } function getField(fieldKey: FieldKey, fieldProps: FieldDeterminationProps): Field | undefined { @@ -169,7 +172,7 @@ function getEmailAddressField({ if (isProgressiveSignUp) { // If there is no ticket, or there is a ticket along with an email, and email address is enabled, // we have to show it in the SignUp form - const show = (!hasTicket || (hasTicket && hasEmail)) && attributes.email_address.enabled; + const show = (!hasTicket || (hasTicket && hasEmail)) && attributes.email_address?.enabled; if (!show) { return; @@ -182,15 +185,15 @@ function getEmailAddressField({ } return { - required: attributes.email_address.required, + required: Boolean(attributes.email_address?.required), disabled: !!hasTicket && !!hasEmail, }; } const show = (!hasTicket || (hasTicket && hasEmail)) && - attributes.email_address.enabled && - attributes.email_address.used_for_first_factor && + attributes.email_address?.enabled && + attributes.email_address?.used_for_first_factor && activeCommIdentifierType === 'emailAddress'; if (!show) { @@ -211,7 +214,7 @@ function getPhoneNumberField({ }: FieldDeterminationProps): Field | undefined { if (isProgressiveSignUp) { // If there is no ticket and phone number is enabled, we have to show it in the SignUp form - const show = attributes.phone_number.enabled; + const show = attributes.phone_number?.enabled; if (!show) { return; @@ -224,13 +227,13 @@ function getPhoneNumberField({ } return { - required: attributes.phone_number.required, + required: Boolean(attributes.phone_number?.required), }; } const show = !hasTicket && - attributes.phone_number.enabled && + attributes.phone_number?.enabled && attributes.phone_number.used_for_first_factor && activeCommIdentifierType === 'phoneNumber'; @@ -244,15 +247,15 @@ function getPhoneNumberField({ } // Currently, password is always enabled so only show if required -function getPasswordField(attributes: Attributes): Field | undefined { - const show = attributes.password.enabled && attributes.password.required; +function getPasswordField(attributes: Partial): Field | undefined { + const show = attributes.password?.enabled && attributes.password.required; if (!show) { return; } return { - required: attributes.password.required, + required: Boolean(attributes.password?.required), }; } @@ -276,16 +279,16 @@ function getLegalAcceptedField(legalConsentRequired?: boolean): Field | undefine }; } -function getGenericField(fieldKey: FieldKey, attributes: Attributes): Field | undefined { +function getGenericField(fieldKey: FieldKey, attributes: Partial): Field | undefined { const attrKey = camelToSnake(fieldKey); // @ts-expect-error - TS doesn't know that the key exists - if (!attributes[attrKey].enabled) { + if (!attributes[attrKey]?.enabled) { return; } return { // @ts-expect-error - TS doesn't know that the key exists - required: attributes[attrKey].required, + required: attributes[attrKey]?.required, }; } diff --git a/packages/clerk-js/src/ui/components/UserProfile/AccountPage.tsx b/packages/clerk-js/src/ui/components/UserProfile/AccountPage.tsx index 5e0f5694784..8dde4b31b59 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/AccountPage.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/AccountPage.tsx @@ -16,12 +16,12 @@ export const AccountPage = withCardStateProvider(() => { const card = useCardState(); const { user } = useUser(); - const showUsername = attributes.username.enabled; - const showEmail = attributes.email_address.enabled; - const showPhone = attributes.phone_number.enabled; + const showUsername = attributes.username?.enabled; + const showEmail = attributes.email_address?.enabled; + const showPhone = attributes.phone_number?.enabled; const showConnectedAccounts = social && Object.values(social).filter(p => p.enabled).length > 0; const showEnterpriseAccounts = user && enterpriseSSO.enabled; - const showWeb3 = attributes.web3_wallet.enabled; + const showWeb3 = attributes.web3_wallet?.enabled; const shouldAllowIdentificationCreation = !showEnterpriseAccounts || diff --git a/packages/clerk-js/src/ui/components/UserProfile/EmailForm.tsx b/packages/clerk-js/src/ui/components/UserProfile/EmailForm.tsx index 43ed2258827..b41659c24b9 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/EmailForm.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/EmailForm.tsx @@ -125,7 +125,7 @@ const getTranslationKeyByStrategy = (strategy: PrepareEmailAddressVerificationPa function isEmailLinksEnabledForInstance(env: EnvironmentResource): boolean { const { userSettings } = env; const { email_address } = userSettings.attributes; - return email_address.enabled && email_address.verifications.includes('email_link'); + return Boolean(email_address?.enabled && email_address?.verifications.includes('email_link')); } /** diff --git a/packages/clerk-js/src/ui/components/UserProfile/MfaPhoneCodeScreen.tsx b/packages/clerk-js/src/ui/components/UserProfile/MfaPhoneCodeScreen.tsx index 2bad9c2e64a..c0c0f803c9b 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/MfaPhoneCodeScreen.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/MfaPhoneCodeScreen.tsx @@ -27,7 +27,7 @@ export const MfaPhoneCodeScreen = withCardStateProvider((props: MfaPhoneCodeScre const ref = React.useRef(); const wizard = useWizard({ defaultStep: 2 }); - const isInstanceWithBackupCodes = useEnvironment().userSettings.attributes.backup_code.enabled; + const isInstanceWithBackupCodes = useEnvironment().userSettings.attributes.backup_code?.enabled; return ( diff --git a/packages/clerk-js/src/ui/components/UserProfile/ProfileForm.tsx b/packages/clerk-js/src/ui/components/UserProfile/ProfileForm.tsx index a14d4254221..b7c0a01260e 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/ProfileForm.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/ProfileForm.tsx @@ -21,8 +21,8 @@ export const ProfileForm = withCardStateProvider((props: ProfileFormProps) => { } const { first_name, last_name } = useEnvironment().userSettings.attributes; - const showFirstName = first_name.enabled; - const showLastName = last_name.enabled; + const showFirstName = first_name?.enabled; + const showLastName = last_name?.enabled; const userFirstName = user.firstName || ''; const userLastName = user.lastName || ''; @@ -30,13 +30,13 @@ export const ProfileForm = withCardStateProvider((props: ProfileFormProps) => { type: 'text', label: localizationKeys('formFieldLabel__firstName'), placeholder: localizationKeys('formFieldInputPlaceholder__firstName'), - isRequired: last_name.required, + isRequired: last_name?.required, }); const lastNameField = useFormControl('lastName', user.lastName || '', { type: 'text', label: localizationKeys('formFieldLabel__lastName'), placeholder: localizationKeys('formFieldInputPlaceholder__lastName'), - isRequired: last_name.required, + isRequired: last_name?.required, }); const userInfoChanged = diff --git a/packages/clerk-js/src/ui/components/UserProfile/SecurityPage.tsx b/packages/clerk-js/src/ui/components/UserProfile/SecurityPage.tsx index 5e668d58d53..bd21f0258d2 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/SecurityPage.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/SecurityPage.tsx @@ -15,7 +15,7 @@ export const SecurityPage = withCardStateProvider(() => { const card = useCardState(); const { user } = useUser(); const showPassword = instanceIsPasswordBased; - const showPasskey = attributes.passkey.enabled; + const showPasskey = attributes.passkey?.enabled; const showMfa = getSecondFactors(attributes).length > 0; const showDelete = user?.deleteSelfEnabled; diff --git a/packages/clerk-js/src/ui/components/UserProfile/UsernameForm.tsx b/packages/clerk-js/src/ui/components/UserProfile/UsernameForm.tsx index 43a36bfd83c..68d46e06830 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/UsernameForm.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/UsernameForm.tsx @@ -26,7 +26,7 @@ export const UsernameForm = withCardStateProvider((props: UsernameFormProps) => return null; } - const isUsernameRequired = userSettings.attributes.username.required; + const isUsernameRequired = userSettings.attributes.username?.required; const canSubmit = (isUsernameRequired ? usernameField.value.length > 0 : true) && user.username !== usernameField.value; diff --git a/packages/clerk-js/src/ui/components/UserProfile/utils.ts b/packages/clerk-js/src/ui/components/UserProfile/utils.ts index 10876cd81f4..1fcc8cbc304 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/utils.ts +++ b/packages/clerk-js/src/ui/components/UserProfile/utils.ts @@ -10,7 +10,7 @@ export const currentSessionFirst = (id: string) => (a: IDable) => (a.id === id ? export const defaultFirst = (a: PhoneNumberResource) => (a.defaultSecondFactor ? -1 : 1); -export function getSecondFactors(attributes: Attributes): string[] { +export function getSecondFactors(attributes: Partial): string[] { const secondFactors: string[] = []; Object.entries(attributes).forEach(([, attr]) => { @@ -22,7 +22,7 @@ export function getSecondFactors(attributes: Attributes): string[] { return secondFactors; } -export function getSecondFactorsAvailableToAdd(attributes: Attributes, user: UserResource): string[] { +export function getSecondFactorsAvailableToAdd(attributes: Partial, user: UserResource): string[] { let sfs = getSecondFactors(attributes); // If user.totp_enabled, skip totp from the list of choices From 1c454b7afe518ebed774d66e523b26f0a5d19935 Mon Sep 17 00:00:00 2001 From: Jacek Date: Thu, 13 Mar 2025 14:29:21 -0500 Subject: [PATCH 02/23] add captchaOauthBypess default --- packages/clerk-js/src/core/resources/DisplayConfig.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/clerk-js/src/core/resources/DisplayConfig.ts b/packages/clerk-js/src/core/resources/DisplayConfig.ts index 4ceb0055f60..52a0da9c40b 100644 --- a/packages/clerk-js/src/core/resources/DisplayConfig.ts +++ b/packages/clerk-js/src/core/resources/DisplayConfig.ts @@ -26,7 +26,7 @@ export class DisplayConfig extends BaseResource implements DisplayConfigResource branded: boolean = false; captchaHeartbeat: boolean = false; captchaHeartbeatIntervalMs?: number; - captchaOauthBypass: OAuthStrategy[] = []; + captchaOauthBypass: OAuthStrategy[] = ['oauth_google', 'oauth_microsoft', 'oauth_apple']; captchaProvider: CaptchaProvider = 'turnstile'; captchaPublicKey: string | null = null; captchaPublicKeyInvisible: string | null = null; From 8f20fdabfa2b7c2cebf6ed5c7c6dba5140b458cd Mon Sep 17 00:00:00 2001 From: Jacek Date: Thu, 13 Mar 2025 14:35:07 -0500 Subject: [PATCH 03/23] wip --- .../__tests__/__snapshots__/Environment.test.ts.snap | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/clerk-js/src/core/resources/__tests__/__snapshots__/Environment.test.ts.snap b/packages/clerk-js/src/core/resources/__tests__/__snapshots__/Environment.test.ts.snap index dfabe474126..42af1b3cf29 100644 --- a/packages/clerk-js/src/core/resources/__tests__/__snapshots__/Environment.test.ts.snap +++ b/packages/clerk-js/src/core/resources/__tests__/__snapshots__/Environment.test.ts.snap @@ -294,7 +294,11 @@ Environment { "backendHost": "", "branded": false, "captchaHeartbeat": false, - "captchaOauthBypass": [], + "captchaOauthBypass": [ + "oauth_google", + "oauth_microsoft", + "oauth_apple", + ], "captchaProvider": "turnstile", "captchaPublicKey": null, "captchaPublicKeyInvisible": null, From b591c926b3ff5cc19e65371e7f6a5ac287270d4c Mon Sep 17 00:00:00 2001 From: Jacek Radko Date: Fri, 14 Mar 2025 11:18:48 -0500 Subject: [PATCH 04/23] Update packages/clerk-js/src/core/clerk.ts --- packages/clerk-js/src/core/clerk.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 8effc91eb6d..52a1b486f0e 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -2073,7 +2073,7 @@ export class Clerk implements ClerkInterface { if (isClerkAPIResponseError(e) && e.errors[0].code === 'requires_captcha') { if (envResult.status === 'rejected') { - await initEnvironmentPromise.catch(() => {}); // Ignore failure + await initEnvironmentPromise; } initComponents(); await initClient(); From 8979e81b6306c479abf513f450964ba4fcddd6d7 Mon Sep 17 00:00:00 2001 From: Jacek Date: Mon, 17 Mar 2025 11:17:18 -0500 Subject: [PATCH 05/23] wip: withDefault helper --- packages/clerk-js/src/core/resources/Base.ts | 4 ++++ .../src/core/resources/OrganizationSettings.ts | 13 ++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/clerk-js/src/core/resources/Base.ts b/packages/clerk-js/src/core/resources/Base.ts index 3db191e9e74..38e1f78954b 100644 --- a/packages/clerk-js/src/core/resources/Base.ts +++ b/packages/clerk-js/src/core/resources/Base.ts @@ -166,6 +166,10 @@ export abstract class BaseResource { protected abstract fromJSON(data: ClerkResourceJSON | null): this; + protected withDefault(value: T | undefined | null, defaultValue: T): T { + return value ?? defaultValue; + } + protected async _baseGet(opts: BaseFetchOptions = {}): Promise { const json = await BaseResource._fetch( { diff --git a/packages/clerk-js/src/core/resources/OrganizationSettings.ts b/packages/clerk-js/src/core/resources/OrganizationSettings.ts index 97436045f02..ea49f111865 100644 --- a/packages/clerk-js/src/core/resources/OrganizationSettings.ts +++ b/packages/clerk-js/src/core/resources/OrganizationSettings.ts @@ -23,7 +23,6 @@ export class OrganizationSettings extends BaseResource implements OrganizationSe public constructor(data: OrganizationSettingsJSON | OrganizationSettingsJSONSnapshot | null = null) { super(); - this.fromJSON(data); } @@ -33,17 +32,17 @@ export class OrganizationSettings extends BaseResource implements OrganizationSe } if (data.actions) { - this.actions.adminDelete = data.actions.admin_delete ?? this.actions.adminDelete; + this.actions.adminDelete = this.withDefault(data.actions.admin_delete, this.actions.adminDelete); } if (data.domains) { - this.domains.enabled = data.domains.enabled ?? this.domains.enabled; - this.domains.enrollmentModes = data.domains.enrollment_modes ?? this.domains.enrollmentModes; - this.domains.defaultRole = data.domains.default_role ?? this.domains.defaultRole; + this.domains.enabled = this.withDefault(data.domains.enabled, this.domains.enabled); + this.domains.enrollmentModes = this.withDefault(data.domains.enrollment_modes, this.domains.enrollmentModes); + this.domains.defaultRole = this.withDefault(data.domains.default_role, this.domains.defaultRole); } - this.enabled = data.enabled ?? this.enabled; - this.maxAllowedMemberships = data.max_allowed_memberships ?? this.maxAllowedMemberships; + this.enabled = this.withDefault(data.enabled, this.enabled); + this.maxAllowedMemberships = this.withDefault(data.max_allowed_memberships, this.maxAllowedMemberships); return this; } From ed5e1ddae7f3fed8c884fa24d97c15d4aebb9cd7 Mon Sep 17 00:00:00 2001 From: Jacek Date: Mon, 17 Mar 2025 11:21:43 -0500 Subject: [PATCH 06/23] typedoc --- packages/clerk-js/src/core/resources/Base.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/clerk-js/src/core/resources/Base.ts b/packages/clerk-js/src/core/resources/Base.ts index 38e1f78954b..0e35e23bd86 100644 --- a/packages/clerk-js/src/core/resources/Base.ts +++ b/packages/clerk-js/src/core/resources/Base.ts @@ -166,6 +166,14 @@ export abstract class BaseResource { protected abstract fromJSON(data: ClerkResourceJSON | null): this; + /** + * Returns the provided value if it is not `undefined` or `null`, otherwise returns the default value. + * + * @template T - The type of the value. + * @param value - The value to check. + * @param defaultValue - The default value to return if the provided value is `undefined` or `null`. + * @returns The provided value if it is not `undefined` or `null`, otherwise the default value. + */ protected withDefault(value: T | undefined | null, defaultValue: T): T { return value ?? defaultValue; } From a45783ce3a866a4732b80f87dc5ed66061e8c87b Mon Sep 17 00:00:00 2001 From: Jacek Date: Mon, 17 Mar 2025 11:24:21 -0500 Subject: [PATCH 07/23] authconfig --- packages/clerk-js/src/core/resources/AuthConfig.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/clerk-js/src/core/resources/AuthConfig.ts b/packages/clerk-js/src/core/resources/AuthConfig.ts index d6b9332ed69..ebda86e93f0 100644 --- a/packages/clerk-js/src/core/resources/AuthConfig.ts +++ b/packages/clerk-js/src/core/resources/AuthConfig.ts @@ -18,9 +18,9 @@ export class AuthConfig extends BaseResource implements AuthConfigResource { if (!data) { return this; } - this.claimedAt = data.claimed_at ? unixEpochToDate(data.claimed_at) : null; - this.reverification = data.reverification ?? false; - this.singleSessionMode = data.single_session_mode ?? false; + this.claimedAt = this.withDefault(data.claimed_at ? unixEpochToDate(data.claimed_at) : null, this.claimedAt); + this.reverification = this.withDefault(data.reverification, this.reverification); + this.singleSessionMode = this.withDefault(data.single_session_mode, this.singleSessionMode); return this; } From b9da1b92c0ebb095ccefbef502ea8db1df082cf4 Mon Sep 17 00:00:00 2001 From: Jacek Date: Mon, 17 Mar 2025 11:29:04 -0500 Subject: [PATCH 08/23] displayconfig --- .../src/core/resources/DisplayConfig.ts | 84 +++++++++++-------- 1 file changed, 48 insertions(+), 36 deletions(-) diff --git a/packages/clerk-js/src/core/resources/DisplayConfig.ts b/packages/clerk-js/src/core/resources/DisplayConfig.ts index 52a0da9c40b..453a2bf90de 100644 --- a/packages/clerk-js/src/core/resources/DisplayConfig.ts +++ b/packages/clerk-js/src/core/resources/DisplayConfig.ts @@ -63,42 +63,54 @@ export class DisplayConfig extends BaseResource implements DisplayConfigResource return this; } - this.afterCreateOrganizationUrl = data.after_create_organization_url ?? this.afterCreateOrganizationUrl; - this.afterJoinWaitlistUrl = data.after_join_waitlist_url ?? this.afterJoinWaitlistUrl; - this.afterLeaveOrganizationUrl = data.after_leave_organization_url ?? this.afterLeaveOrganizationUrl; - this.afterSignInUrl = data.after_sign_in_url ?? this.afterSignInUrl; - this.afterSignOutAllUrl = data.after_sign_out_all_url ?? this.afterSignOutAllUrl; - this.afterSignOutOneUrl = data.after_sign_out_one_url ?? this.afterSignOutOneUrl; - this.afterSignUpUrl = data.after_sign_up_url ?? this.afterSignUpUrl; - this.afterSwitchSessionUrl = data.after_switch_session_url ?? this.afterSwitchSessionUrl; - this.applicationName = data.application_name ?? this.applicationName; - this.branded = data.branded ?? this.branded; - this.captchaHeartbeat = data.captcha_heartbeat ?? this.captchaHeartbeat; - this.captchaHeartbeatIntervalMs = data.captcha_heartbeat_interval_ms ?? this.captchaHeartbeatIntervalMs; - this.captchaOauthBypass = data.captcha_oauth_bypass ?? this.captchaOauthBypass; - this.captchaProvider = data.captcha_provider ?? this.captchaProvider; - this.captchaPublicKey = data.captcha_public_key ?? this.captchaPublicKey; - this.captchaPublicKeyInvisible = data.captcha_public_key_invisible ?? this.captchaPublicKeyInvisible; - this.captchaWidgetType = data.captcha_widget_type ?? this.captchaWidgetType; - this.clerkJSVersion = data.clerk_js_version ?? this.clerkJSVersion; - this.createOrganizationUrl = data.create_organization_url ?? this.createOrganizationUrl; - this.faviconImageUrl = data.favicon_image_url ?? this.faviconImageUrl; - this.googleOneTapClientId = data.google_one_tap_client_id ?? this.googleOneTapClientId; - this.homeUrl = data.home_url ?? this.homeUrl; - this.id = data.id ?? this.id; - this.instanceEnvironmentType = data.instance_environment_type ?? this.instanceEnvironmentType; - this.logoImageUrl = data.logo_image_url ?? this.logoImageUrl; - this.organizationProfileUrl = data.organization_profile_url ?? this.organizationProfileUrl; - this.preferredSignInStrategy = data.preferred_sign_in_strategy ?? this.preferredSignInStrategy; - this.privacyPolicyUrl = data.privacy_policy_url ?? this.privacyPolicyUrl; - this.showDevModeWarning = data.show_devmode_warning ?? this.showDevModeWarning; - this.signInUrl = data.sign_in_url ?? this.signInUrl; - this.signUpUrl = data.sign_up_url ?? this.signUpUrl; - this.supportEmail = data.support_email ?? this.supportEmail; - this.termsUrl = data.terms_url ?? this.termsUrl; - this.theme = data.theme ?? this.theme; - this.userProfileUrl = data.user_profile_url ?? this.userProfileUrl; - this.waitlistUrl = data.waitlist_url ?? this.waitlistUrl; + this.afterCreateOrganizationUrl = this.withDefault( + data.after_create_organization_url, + this.afterCreateOrganizationUrl, + ); + this.afterJoinWaitlistUrl = this.withDefault(data.after_join_waitlist_url, this.afterJoinWaitlistUrl); + this.afterLeaveOrganizationUrl = this.withDefault( + data.after_leave_organization_url, + this.afterLeaveOrganizationUrl, + ); + this.afterSignInUrl = this.withDefault(data.after_sign_in_url, this.afterSignInUrl); + this.afterSignOutAllUrl = this.withDefault(data.after_sign_out_all_url, this.afterSignOutAllUrl); + this.afterSignOutOneUrl = this.withDefault(data.after_sign_out_one_url, this.afterSignOutOneUrl); + this.afterSignUpUrl = this.withDefault(data.after_sign_up_url, this.afterSignUpUrl); + this.afterSwitchSessionUrl = this.withDefault(data.after_switch_session_url, this.afterSwitchSessionUrl); + this.applicationName = this.withDefault(data.application_name, this.applicationName); + this.branded = this.withDefault(data.branded, this.branded); + this.captchaHeartbeat = this.withDefault(data.captcha_heartbeat, this.captchaHeartbeat); + this.captchaHeartbeatIntervalMs = this.withDefault( + data.captcha_heartbeat_interval_ms, + this.captchaHeartbeatIntervalMs, + ); + this.captchaOauthBypass = this.withDefault(data.captcha_oauth_bypass, this.captchaOauthBypass); + this.captchaProvider = this.withDefault(data.captcha_provider, this.captchaProvider); + this.captchaPublicKey = this.withDefault(data.captcha_public_key, this.captchaPublicKey); + this.captchaPublicKeyInvisible = this.withDefault( + data.captcha_public_key_invisible, + this.captchaPublicKeyInvisible, + ); + this.captchaWidgetType = this.withDefault(data.captcha_widget_type, this.captchaWidgetType); + this.clerkJSVersion = this.withDefault(data.clerk_js_version, this.clerkJSVersion); + this.createOrganizationUrl = this.withDefault(data.create_organization_url, this.createOrganizationUrl); + this.faviconImageUrl = this.withDefault(data.favicon_image_url, this.faviconImageUrl); + this.googleOneTapClientId = this.withDefault(data.google_one_tap_client_id, this.googleOneTapClientId); + this.homeUrl = this.withDefault(data.home_url, this.homeUrl); + this.id = this.withDefault(data.id, this.id); + this.instanceEnvironmentType = this.withDefault(data.instance_environment_type, this.instanceEnvironmentType); + this.logoImageUrl = this.withDefault(data.logo_image_url, this.logoImageUrl); + this.organizationProfileUrl = this.withDefault(data.organization_profile_url, this.organizationProfileUrl); + this.preferredSignInStrategy = this.withDefault(data.preferred_sign_in_strategy, this.preferredSignInStrategy); + this.privacyPolicyUrl = this.withDefault(data.privacy_policy_url, this.privacyPolicyUrl); + this.showDevModeWarning = this.withDefault(data.show_devmode_warning, this.showDevModeWarning); + this.signInUrl = this.withDefault(data.sign_in_url, this.signInUrl); + this.signUpUrl = this.withDefault(data.sign_up_url, this.signUpUrl); + this.supportEmail = this.withDefault(data.support_email, this.supportEmail); + this.termsUrl = this.withDefault(data.terms_url, this.termsUrl); + this.theme = this.withDefault(data.theme, this.theme); + this.userProfileUrl = this.withDefault(data.user_profile_url, this.userProfileUrl); + this.waitlistUrl = this.withDefault(data.waitlist_url, this.waitlistUrl); return this; } From e9d3a8028943a715156a75361dc21de831fb9ac0 Mon Sep 17 00:00:00 2001 From: Jacek Date: Mon, 17 Mar 2025 14:40:15 -0500 Subject: [PATCH 09/23] Update userSettings --- .../src/core/resources/Environment.ts | 4 +- .../src/core/resources/UserSettings.ts | 178 ++++++++++++++---- 2 files changed, 139 insertions(+), 43 deletions(-) diff --git a/packages/clerk-js/src/core/resources/Environment.ts b/packages/clerk-js/src/core/resources/Environment.ts index aa352539dc0..7c4cf4998d8 100644 --- a/packages/clerk-js/src/core/resources/Environment.ts +++ b/packages/clerk-js/src/core/resources/Environment.ts @@ -19,7 +19,6 @@ export class Environment extends BaseResource implements EnvironmentResource { displayConfig: DisplayConfigResource = new DisplayConfig(); maintenanceMode: boolean = false; pathRoot = '/environment'; - // @ts-expect-error - This is a partial object, but we want to ensure that all attributes are present. userSettings: UserSettingsResource = new UserSettings(); organizationSettings: OrganizationSettingsResource = new OrganizationSettings(); __experimental_commerceSettings: __experimental_CommerceSettingsResource = new __experimental_CommerceSettings(); @@ -45,9 +44,8 @@ export class Environment extends BaseResource implements EnvironmentResource { this.authConfig = new AuthConfig(data.auth_config); this.displayConfig = new DisplayConfig(data.display_config); - this.maintenanceMode = data.maintenance_mode ?? this.maintenanceMode; + this.maintenanceMode = this.withDefault(data.maintenance_mode, this.maintenanceMode); this.organizationSettings = new OrganizationSettings(data.organization_settings); - // @ts-expect-error - This is a partial object, but we want to ensure that all attributes are present. this.userSettings = new UserSettings(data.user_settings); this.__experimental_commerceSettings = new __experimental_CommerceSettings(data.commerce_settings); diff --git a/packages/clerk-js/src/core/resources/UserSettings.ts b/packages/clerk-js/src/core/resources/UserSettings.ts index 37d127d0ef2..92ddaab4917 100644 --- a/packages/clerk-js/src/core/resources/UserSettings.ts +++ b/packages/clerk-js/src/core/resources/UserSettings.ts @@ -28,22 +28,107 @@ export type Actions = { delete_self: boolean; }; +const DISABLED_ATTRIBUTE = { + enabled: false, + first_factors: [], + name: 'phone_number', + required: false, + second_factors: [], + used_for_first_factor: false, + used_for_second_factor: false, + verifications: [], + verify_at_sign_up: false, +}; + /** * @internal */ export class UserSettings extends BaseResource implements UserSettingsResource { id = undefined; - // @ts-expect-error - This is a partial object, but we want to ensure that all actions are present. - actions: Partial = {}; - // @ts-expect-error - This is a partial object, but we want to ensure that all attributes are present. - attributes: Partial = {}; - enterpriseSSO: EnterpriseSSOSettings = {} as EnterpriseSSOSettings; - passkeySettings: PasskeySettingsData = {} as PasskeySettingsData; + actions: Actions = { create_organization: false, delete_self: false }; + attributes: Attributes = { + email_address: { + enabled: true, + first_factors: ['email_code'], + name: 'email_address', + required: true, + second_factors: [], + used_for_first_factor: true, + used_for_second_factor: false, + verifications: ['email_code'], + verify_at_sign_up: true, + }, + phone_number: { + ...DISABLED_ATTRIBUTE, + name: 'phone_number', + }, + username: { + ...DISABLED_ATTRIBUTE, + + name: 'username', + }, + web3_wallet: { + ...DISABLED_ATTRIBUTE, + + name: 'web3_wallet', + }, + first_name: { + ...DISABLED_ATTRIBUTE, + name: 'first_name', + }, + last_name: { + ...DISABLED_ATTRIBUTE, + name: 'last_name', + }, + password: { + enabled: true, + first_factors: [], + name: 'password', + required: true, + second_factors: [], + used_for_first_factor: false, + used_for_second_factor: false, + verifications: [], + verify_at_sign_up: false, + }, + authenticator_app: { + ...DISABLED_ATTRIBUTE, + name: 'authenticator_app', + }, + backup_code: { + ...DISABLED_ATTRIBUTE, + name: 'backup_code', + }, + passkey: { + ...DISABLED_ATTRIBUTE, + name: 'passkey', + }, + }; + enterpriseSSO: EnterpriseSSOSettings = { + enabled: false, + }; + passkeySettings: PasskeySettingsData = { + allow_autofill: false, + show_sign_in_button: false, + }; passwordSettings: PasswordSettingsData = {} as PasswordSettingsData; - saml: SamlSettings = {} as SamlSettings; - signIn: SignInData = {} as SignInData; - signUp: SignUpData = {} as SignUpData; + saml: SamlSettings = { + enabled: false, + }; + signIn: SignInData = { + second_factor: { + required: false, + enabled: false, + }, + }; + signUp: SignUpData = { + allowlist_only: false, + captcha_enabled: false, + legal_consent_enabled: false, + mode: 'public', + progressive: true, + }; social: OAuthProviders = {} as OAuthProviders; usernameSettings: UsernameSettingsData = {} as UsernameSettingsData; @@ -110,51 +195,64 @@ export class UserSettings extends BaseResource implements UserSettingsResource { ); } - /** - * fromJSON now safely returns the instance even if null is provided. - */ protected fromJSON(data: UserSettingsJSON | UserSettingsJSONSnapshot | null): this { if (!data) { return this; } - this.attributes = Object.fromEntries( - Object.entries(data.attributes).map(a => [a[0], { ...a[1], name: a[0] }]), - ) as Attributes; - this.actions = data.actions; - this.enterpriseSSO = data.enterprise_sso; - this.passkeySettings = data.passkey_settings; - this.passwordSettings = { - ...data.password_settings, - min_length: Math.max(data.password_settings?.min_length ?? defaultMinPasswordLength, defaultMinPasswordLength), - max_length: - data.password_settings?.max_length === 0 - ? defaultMaxPasswordLength - : Math.min(data.password_settings?.max_length ?? defaultMaxPasswordLength, defaultMaxPasswordLength), - }; - this.saml = data.saml; - this.signIn = data.sign_in; - this.signUp = data.sign_up; - this.social = data.social; - this.usernameSettings = { - ...data.username_settings, - min_length: Math.max(data.username_settings?.min_length ?? defaultMinUsernameLength, defaultMinUsernameLength), - max_length: Math.min(data.username_settings?.max_length ?? defaultMaxUsernameLength, defaultMaxUsernameLength), - }; + this.attributes = this.withDefault( + data.attributes + ? (Object.fromEntries(Object.entries(data.attributes).map(a => [a[0], { ...a[1], name: a[0] }])) as Attributes) + : null, + this.attributes, + ); + this.actions = this.withDefault(data.actions, this.actions); + this.enterpriseSSO = this.withDefault(data.enterprise_sso, this.enterpriseSSO); + this.passkeySettings = this.withDefault(data.passkey_settings, this.passkeySettings); + this.passwordSettings = data.password_settings + ? { + ...data.password_settings, + min_length: Math.max( + data.password_settings?.min_length ?? defaultMinPasswordLength, + defaultMinPasswordLength, + ), + max_length: + data.password_settings?.max_length === 0 + ? defaultMaxPasswordLength + : Math.min(data.password_settings?.max_length ?? defaultMaxPasswordLength, defaultMaxPasswordLength), + } + : this.passwordSettings; + this.saml = this.withDefault(data.saml, this.saml); + this.signIn = this.withDefault(data.sign_in, this.signIn); + this.signUp = this.withDefault(data.sign_up, this.signUp); + this.social = this.withDefault(data.social, this.social); + this.usernameSettings = data.username_settings + ? { + ...data.username_settings, + min_length: Math.max( + data.username_settings?.min_length ?? defaultMinUsernameLength, + defaultMinUsernameLength, + ), + max_length: Math.min( + data.username_settings?.max_length ?? defaultMaxUsernameLength, + defaultMaxUsernameLength, + ), + } + : this.usernameSettings; return this; } public __internal_toSnapshot(): UserSettingsJSONSnapshot { return { - social: this.social, - saml: this.saml, - attributes: this.attributes, actions: this.actions, + attributes: this.attributes, + passkey_settings: this.passkeySettings, + password_settings: this.passwordSettings, + saml: this.saml, sign_in: this.signIn, sign_up: this.signUp, - password_settings: this.passwordSettings, - passkey_settings: this.passkeySettings, + social: this.social, } as unknown as UserSettingsJSONSnapshot; } } From 5471c2e44705c54ed0c40e89862d36fc15863a73 Mon Sep 17 00:00:00 2001 From: Jacek Date: Mon, 17 Mar 2025 14:43:36 -0500 Subject: [PATCH 10/23] wip --- packages/clerk-js/bundlewatch.config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/clerk-js/bundlewatch.config.json b/packages/clerk-js/bundlewatch.config.json index 350134b9589..2f60b7db1fd 100644 --- a/packages/clerk-js/bundlewatch.config.json +++ b/packages/clerk-js/bundlewatch.config.json @@ -1,6 +1,6 @@ { "files": [ - { "path": "./dist/clerk.js", "maxSize": "575kB" }, + { "path": "./dist/clerk.js", "maxSize": "575.5kB" }, { "path": "./dist/clerk.browser.js", "maxSize": "78kB" }, { "path": "./dist/clerk.headless.js", "maxSize": "50.5KB" }, { "path": "./dist/ui-common*.js", "maxSize": "93KB" }, From fc0b9f6fca032cbf76fb5881f332892825743045 Mon Sep 17 00:00:00 2001 From: Jacek Date: Mon, 17 Mar 2025 14:55:02 -0500 Subject: [PATCH 11/23] snappies --- .../__snapshots__/Environment.test.ts.snap | 159 ++++++++++++++++-- .../__snapshots__/UserSettings.test.ts.snap | 150 ++++++++++++++++- 2 files changed, 290 insertions(+), 19 deletions(-) diff --git a/packages/clerk-js/src/core/resources/__tests__/__snapshots__/Environment.test.ts.snap b/packages/clerk-js/src/core/resources/__tests__/__snapshots__/Environment.test.ts.snap index 42af1b3cf29..1fe9cb48a80 100644 --- a/packages/clerk-js/src/core/resources/__tests__/__snapshots__/Environment.test.ts.snap +++ b/packages/clerk-js/src/core/resources/__tests__/__snapshots__/Environment.test.ts.snap @@ -342,16 +342,152 @@ Environment { }, "pathRoot": "/environment", "userSettings": UserSettings { - "actions": {}, - "attributes": {}, - "enterpriseSSO": {}, + "actions": { + "create_organization": false, + "delete_self": false, + }, + "attributes": { + "authenticator_app": { + "enabled": false, + "first_factors": [], + "name": "authenticator_app", + "required": false, + "second_factors": [], + "used_for_first_factor": false, + "used_for_second_factor": false, + "verifications": [], + "verify_at_sign_up": false, + }, + "backup_code": { + "enabled": false, + "first_factors": [], + "name": "backup_code", + "required": false, + "second_factors": [], + "used_for_first_factor": false, + "used_for_second_factor": false, + "verifications": [], + "verify_at_sign_up": false, + }, + "email_address": { + "enabled": true, + "first_factors": [ + "email_code", + ], + "name": "email_address", + "required": true, + "second_factors": [], + "used_for_first_factor": true, + "used_for_second_factor": false, + "verifications": [ + "email_code", + ], + "verify_at_sign_up": true, + }, + "first_name": { + "enabled": false, + "first_factors": [], + "name": "first_name", + "required": false, + "second_factors": [], + "used_for_first_factor": false, + "used_for_second_factor": false, + "verifications": [], + "verify_at_sign_up": false, + }, + "last_name": { + "enabled": false, + "first_factors": [], + "name": "last_name", + "required": false, + "second_factors": [], + "used_for_first_factor": false, + "used_for_second_factor": false, + "verifications": [], + "verify_at_sign_up": false, + }, + "passkey": { + "enabled": false, + "first_factors": [], + "name": "passkey", + "required": false, + "second_factors": [], + "used_for_first_factor": false, + "used_for_second_factor": false, + "verifications": [], + "verify_at_sign_up": false, + }, + "password": { + "enabled": true, + "first_factors": [], + "name": "password", + "required": true, + "second_factors": [], + "used_for_first_factor": false, + "used_for_second_factor": false, + "verifications": [], + "verify_at_sign_up": false, + }, + "phone_number": { + "enabled": false, + "first_factors": [], + "name": "phone_number", + "required": false, + "second_factors": [], + "used_for_first_factor": false, + "used_for_second_factor": false, + "verifications": [], + "verify_at_sign_up": false, + }, + "username": { + "enabled": false, + "first_factors": [], + "name": "username", + "required": false, + "second_factors": [], + "used_for_first_factor": false, + "used_for_second_factor": false, + "verifications": [], + "verify_at_sign_up": false, + }, + "web3_wallet": { + "enabled": false, + "first_factors": [], + "name": "web3_wallet", + "required": false, + "second_factors": [], + "used_for_first_factor": false, + "used_for_second_factor": false, + "verifications": [], + "verify_at_sign_up": false, + }, + }, + "enterpriseSSO": { + "enabled": false, + }, "id": undefined, - "passkeySettings": {}, + "passkeySettings": { + "allow_autofill": false, + "show_sign_in_button": false, + }, "passwordSettings": {}, "pathRoot": "", - "saml": {}, - "signIn": {}, - "signUp": {}, + "saml": { + "enabled": false, + }, + "signIn": { + "second_factor": { + "enabled": false, + "required": false, + }, + }, + "signUp": { + "allowlist_only": false, + "captcha_enabled": false, + "legal_consent_enabled": false, + "mode": "public", + "progressive": true, + }, "social": {}, "usernameSettings": {}, }, @@ -582,7 +718,9 @@ Environment { "verify_at_sign_up": false, }, }, - "enterpriseSSO": undefined, + "enterpriseSSO": { + "enabled": false, + }, "id": undefined, "passkeySettings": { "allow_autofill": true, @@ -631,10 +769,7 @@ Environment { "strategy": "oauth_google", }, }, - "usernameSettings": { - "max_length": 64, - "min_length": 4, - }, + "usernameSettings": {}, }, } `; diff --git a/packages/clerk-js/src/core/resources/__tests__/__snapshots__/UserSettings.test.ts.snap b/packages/clerk-js/src/core/resources/__tests__/__snapshots__/UserSettings.test.ts.snap index 0616b18a080..5ed6282b2f3 100644 --- a/packages/clerk-js/src/core/resources/__tests__/__snapshots__/UserSettings.test.ts.snap +++ b/packages/clerk-js/src/core/resources/__tests__/__snapshots__/UserSettings.test.ts.snap @@ -2,16 +2,152 @@ exports[`UserSettings defaults values when instantiated with no arguments 1`] = ` UserSettings { - "actions": {}, - "attributes": {}, - "enterpriseSSO": {}, + "actions": { + "create_organization": false, + "delete_self": false, + }, + "attributes": { + "authenticator_app": { + "enabled": false, + "first_factors": [], + "name": "authenticator_app", + "required": false, + "second_factors": [], + "used_for_first_factor": false, + "used_for_second_factor": false, + "verifications": [], + "verify_at_sign_up": false, + }, + "backup_code": { + "enabled": false, + "first_factors": [], + "name": "backup_code", + "required": false, + "second_factors": [], + "used_for_first_factor": false, + "used_for_second_factor": false, + "verifications": [], + "verify_at_sign_up": false, + }, + "email_address": { + "enabled": true, + "first_factors": [ + "email_code", + ], + "name": "email_address", + "required": true, + "second_factors": [], + "used_for_first_factor": true, + "used_for_second_factor": false, + "verifications": [ + "email_code", + ], + "verify_at_sign_up": true, + }, + "first_name": { + "enabled": false, + "first_factors": [], + "name": "first_name", + "required": false, + "second_factors": [], + "used_for_first_factor": false, + "used_for_second_factor": false, + "verifications": [], + "verify_at_sign_up": false, + }, + "last_name": { + "enabled": false, + "first_factors": [], + "name": "last_name", + "required": false, + "second_factors": [], + "used_for_first_factor": false, + "used_for_second_factor": false, + "verifications": [], + "verify_at_sign_up": false, + }, + "passkey": { + "enabled": false, + "first_factors": [], + "name": "passkey", + "required": false, + "second_factors": [], + "used_for_first_factor": false, + "used_for_second_factor": false, + "verifications": [], + "verify_at_sign_up": false, + }, + "password": { + "enabled": true, + "first_factors": [], + "name": "password", + "required": true, + "second_factors": [], + "used_for_first_factor": false, + "used_for_second_factor": false, + "verifications": [], + "verify_at_sign_up": false, + }, + "phone_number": { + "enabled": false, + "first_factors": [], + "name": "phone_number", + "required": false, + "second_factors": [], + "used_for_first_factor": false, + "used_for_second_factor": false, + "verifications": [], + "verify_at_sign_up": false, + }, + "username": { + "enabled": false, + "first_factors": [], + "name": "username", + "required": false, + "second_factors": [], + "used_for_first_factor": false, + "used_for_second_factor": false, + "verifications": [], + "verify_at_sign_up": false, + }, + "web3_wallet": { + "enabled": false, + "first_factors": [], + "name": "web3_wallet", + "required": false, + "second_factors": [], + "used_for_first_factor": false, + "used_for_second_factor": false, + "verifications": [], + "verify_at_sign_up": false, + }, + }, + "enterpriseSSO": { + "enabled": false, + }, "id": undefined, - "passkeySettings": {}, + "passkeySettings": { + "allow_autofill": false, + "show_sign_in_button": false, + }, "passwordSettings": {}, "pathRoot": "", - "saml": {}, - "signIn": {}, - "signUp": {}, + "saml": { + "enabled": false, + }, + "signIn": { + "second_factor": { + "enabled": false, + "required": false, + }, + }, + "signUp": { + "allowlist_only": false, + "captcha_enabled": false, + "legal_consent_enabled": false, + "mode": "public", + "progressive": true, + }, "social": {}, "usernameSettings": {}, } From b20008d97d36446b33dfa676557695cde26696af Mon Sep 17 00:00:00 2001 From: Jacek Date: Mon, 17 Mar 2025 15:12:51 -0500 Subject: [PATCH 12/23] authconfig test --- .../resources/__tests__/AuthConfig.test.ts | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 packages/clerk-js/src/core/resources/__tests__/AuthConfig.test.ts diff --git a/packages/clerk-js/src/core/resources/__tests__/AuthConfig.test.ts b/packages/clerk-js/src/core/resources/__tests__/AuthConfig.test.ts new file mode 100644 index 00000000000..d97a8bf3980 --- /dev/null +++ b/packages/clerk-js/src/core/resources/__tests__/AuthConfig.test.ts @@ -0,0 +1,53 @@ +import { unixEpochToDate } from '../../../utils/date'; +import { AuthConfig } from '../AuthConfig'; + +jest.mock('../../../utils/date', () => ({ + unixEpochToDate: jest.fn(timestamp => new Date(timestamp)), +})); + +describe('AuthConfig', () => { + it('should initialize with default values', () => { + const authConfig = new AuthConfig(); + + expect(authConfig.claimedAt).toBeNull(); + expect(authConfig.reverification).toBe(false); + expect(authConfig.singleSessionMode).toBe(false); + }); + + it('should initialize with provided values', () => { + const mockData = { + object: 'auth_config', + id: '', + claimed_at: 1672531200000, + reverification: true, + single_session_mode: true, + }; + + const authConfig = new AuthConfig(mockData); + + expect(unixEpochToDate).toHaveBeenCalledWith(1672531200000); + expect(authConfig.claimedAt).toEqual(new Date(1672531200000)); + expect(authConfig.reverification).toBe(true); + expect(authConfig.singleSessionMode).toBe(true); + }); + + it('should convert to JSON snapshot correctly', () => { + const authConfig = new AuthConfig({ + object: 'auth_config', + id: '', + claimed_at: 1672531200000, + reverification: true, + single_session_mode: true, + }); + + const snapshot = authConfig.__internal_toSnapshot(); + + expect(snapshot).toEqual({ + object: 'auth_config', + claimed_at: 1672531200000, + id: '', + reverification: true, + single_session_mode: true, + }); + }); +}); From 077a26f920b5beddfd2ebdfce27fe43c3d26e8ff Mon Sep 17 00:00:00 2001 From: Jacek Date: Mon, 17 Mar 2025 15:13:19 -0500 Subject: [PATCH 13/23] wip --- .../src/core/resources/__tests__/AuthConfig.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/clerk-js/src/core/resources/__tests__/AuthConfig.test.ts b/packages/clerk-js/src/core/resources/__tests__/AuthConfig.test.ts index d97a8bf3980..b21330d5d75 100644 --- a/packages/clerk-js/src/core/resources/__tests__/AuthConfig.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/AuthConfig.test.ts @@ -6,7 +6,7 @@ jest.mock('../../../utils/date', () => ({ })); describe('AuthConfig', () => { - it('should initialize with default values', () => { + it('initializes with default values', () => { const authConfig = new AuthConfig(); expect(authConfig.claimedAt).toBeNull(); @@ -14,7 +14,7 @@ describe('AuthConfig', () => { expect(authConfig.singleSessionMode).toBe(false); }); - it('should initialize with provided values', () => { + it('initializes with provided values', () => { const mockData = { object: 'auth_config', id: '', @@ -31,7 +31,7 @@ describe('AuthConfig', () => { expect(authConfig.singleSessionMode).toBe(true); }); - it('should convert to JSON snapshot correctly', () => { + it('converts to JSON snapshot correctly', () => { const authConfig = new AuthConfig({ object: 'auth_config', id: '', From 3195ab0795227f5c71b647572365ebdc06fece45 Mon Sep 17 00:00:00 2001 From: Jacek Date: Mon, 17 Mar 2025 15:18:13 -0500 Subject: [PATCH 14/23] wip --- packages/clerk-js/src/core/resources/AuthConfig.ts | 8 +++++--- .../src/core/resources/__tests__/AuthConfig.test.ts | 4 ---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/clerk-js/src/core/resources/AuthConfig.ts b/packages/clerk-js/src/core/resources/AuthConfig.ts index ebda86e93f0..7e1c53cd8b8 100644 --- a/packages/clerk-js/src/core/resources/AuthConfig.ts +++ b/packages/clerk-js/src/core/resources/AuthConfig.ts @@ -5,16 +5,18 @@ import { BaseResource } from './internal'; export class AuthConfig extends BaseResource implements AuthConfigResource { claimedAt: Date | null = null; + id = ''; + object = 'auth_config' as const; reverification: boolean = false; singleSessionMode: boolean = false; - public constructor(data: AuthConfigJSON | null = null) { + public constructor(data: Partial | null = null) { super(); this.fromJSON(data); } - protected fromJSON(data: AuthConfigJSON | null): this { + protected fromJSON(data: Partial | null): this { if (!data) { return this; } @@ -26,9 +28,9 @@ export class AuthConfig extends BaseResource implements AuthConfigResource { public __internal_toSnapshot(): AuthConfigJSONSnapshot { return { - object: 'auth_config', claimed_at: this.claimedAt ? this.claimedAt.getTime() : null, id: this.id ?? '', + object: this.object, reverification: this.reverification, single_session_mode: this.singleSessionMode, }; diff --git a/packages/clerk-js/src/core/resources/__tests__/AuthConfig.test.ts b/packages/clerk-js/src/core/resources/__tests__/AuthConfig.test.ts index b21330d5d75..95a50f77110 100644 --- a/packages/clerk-js/src/core/resources/__tests__/AuthConfig.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/AuthConfig.test.ts @@ -16,8 +16,6 @@ describe('AuthConfig', () => { it('initializes with provided values', () => { const mockData = { - object: 'auth_config', - id: '', claimed_at: 1672531200000, reverification: true, single_session_mode: true, @@ -33,8 +31,6 @@ describe('AuthConfig', () => { it('converts to JSON snapshot correctly', () => { const authConfig = new AuthConfig({ - object: 'auth_config', - id: '', claimed_at: 1672531200000, reverification: true, single_session_mode: true, From e793ae02de8cf3ee38b5f84ebfc4767fa4a27727 Mon Sep 17 00:00:00 2001 From: Jacek Date: Mon, 17 Mar 2025 15:18:57 -0500 Subject: [PATCH 15/23] wip --- packages/clerk-js/src/core/resources/AuthConfig.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/clerk-js/src/core/resources/AuthConfig.ts b/packages/clerk-js/src/core/resources/AuthConfig.ts index 7e1c53cd8b8..ba28475071b 100644 --- a/packages/clerk-js/src/core/resources/AuthConfig.ts +++ b/packages/clerk-js/src/core/resources/AuthConfig.ts @@ -6,7 +6,6 @@ import { BaseResource } from './internal'; export class AuthConfig extends BaseResource implements AuthConfigResource { claimedAt: Date | null = null; id = ''; - object = 'auth_config' as const; reverification: boolean = false; singleSessionMode: boolean = false; @@ -30,7 +29,7 @@ export class AuthConfig extends BaseResource implements AuthConfigResource { return { claimed_at: this.claimedAt ? this.claimedAt.getTime() : null, id: this.id ?? '', - object: this.object, + object: 'auth_config', reverification: this.reverification, single_session_mode: this.singleSessionMode, }; From 2be42764d8d6a27a15382caed0f8098fa8ac23c3 Mon Sep 17 00:00:00 2001 From: Jacek Date: Mon, 17 Mar 2025 15:37:00 -0500 Subject: [PATCH 16/23] wip --- packages/clerk-js/src/core/resources/AuthConfig.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/clerk-js/src/core/resources/AuthConfig.ts b/packages/clerk-js/src/core/resources/AuthConfig.ts index ba28475071b..2b07dcb61ce 100644 --- a/packages/clerk-js/src/core/resources/AuthConfig.ts +++ b/packages/clerk-js/src/core/resources/AuthConfig.ts @@ -5,7 +5,6 @@ import { BaseResource } from './internal'; export class AuthConfig extends BaseResource implements AuthConfigResource { claimedAt: Date | null = null; - id = ''; reverification: boolean = false; singleSessionMode: boolean = false; From b9435f62158e584da79a4265bb15b7bfacc6eccb Mon Sep 17 00:00:00 2001 From: Jacek Date: Mon, 17 Mar 2025 15:47:07 -0500 Subject: [PATCH 17/23] wip --- packages/clerk-js/bundlewatch.config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/clerk-js/bundlewatch.config.json b/packages/clerk-js/bundlewatch.config.json index d640cefb2c2..d5308ea75e5 100644 --- a/packages/clerk-js/bundlewatch.config.json +++ b/packages/clerk-js/bundlewatch.config.json @@ -2,7 +2,7 @@ "files": [ { "path": "./dist/clerk.js", "maxSize": "576kB" }, { "path": "./dist/clerk.browser.js", "maxSize": "78kB" }, - { "path": "./dist/clerk.headless.js", "maxSize": "50KB" }, + { "path": "./dist/clerk.headless.js", "maxSize": "50.5KB" }, { "path": "./dist/ui-common*.js", "maxSize": "94KB" }, { "path": "./dist/vendors*.js", "maxSize": "30KB" }, { "path": "./dist/coinbase*.js", "maxSize": "35.5KB" }, From e08bff0e1e7a43d3cf0e4d3e6fe9c0c53f0485e6 Mon Sep 17 00:00:00 2001 From: Jacek Date: Mon, 17 Mar 2025 16:01:06 -0500 Subject: [PATCH 18/23] wip --- packages/clerk-js/bundlewatch.config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/clerk-js/bundlewatch.config.json b/packages/clerk-js/bundlewatch.config.json index d5308ea75e5..cdbe4f782ac 100644 --- a/packages/clerk-js/bundlewatch.config.json +++ b/packages/clerk-js/bundlewatch.config.json @@ -2,7 +2,7 @@ "files": [ { "path": "./dist/clerk.js", "maxSize": "576kB" }, { "path": "./dist/clerk.browser.js", "maxSize": "78kB" }, - { "path": "./dist/clerk.headless.js", "maxSize": "50.5KB" }, + { "path": "./dist/clerk.headless.js", "maxSize": "51KB" }, { "path": "./dist/ui-common*.js", "maxSize": "94KB" }, { "path": "./dist/vendors*.js", "maxSize": "30KB" }, { "path": "./dist/coinbase*.js", "maxSize": "35.5KB" }, From fb8654b6311f21a76f1b54e28824b5e018957905 Mon Sep 17 00:00:00 2001 From: Jacek Date: Tue, 18 Mar 2025 11:21:12 -0500 Subject: [PATCH 19/23] PR feedback from @LekoArts --- .changeset/dirty-lizards-count.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/dirty-lizards-count.md b/.changeset/dirty-lizards-count.md index fa310cd89bb..a999cf46882 100644 --- a/.changeset/dirty-lizards-count.md +++ b/.changeset/dirty-lizards-count.md @@ -2,4 +2,4 @@ '@clerk/clerk-js': minor --- -Allow initialization of Clerk.environment even if the API call to get environment details fails +Improve the resilience of the SDK against situations where the /v1/environment endpoint is not reachable. This is achieved by allowing the initialization of the environment with default values. From 1d9edbf9e566b09334fad8df78b94deb9b61059f Mon Sep 17 00:00:00 2001 From: Jacek Date: Tue, 18 Mar 2025 11:22:04 -0500 Subject: [PATCH 20/23] feedback --- packages/clerk-js/src/core/resources/Environment.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/clerk-js/src/core/resources/Environment.ts b/packages/clerk-js/src/core/resources/Environment.ts index 7c4cf4998d8..f589088a749 100644 --- a/packages/clerk-js/src/core/resources/Environment.ts +++ b/packages/clerk-js/src/core/resources/Environment.ts @@ -77,7 +77,7 @@ export class Environment extends BaseResource implements EnvironmentResource { object: 'environment', auth_config: this.authConfig.__internal_toSnapshot(), display_config: this.displayConfig.__internal_toSnapshot(), - id: this.id || '', + id: this.id ?? '', maintenance_mode: this.maintenanceMode, organization_settings: this.organizationSettings.__internal_toSnapshot(), user_settings: this.userSettings.__internal_toSnapshot(), From e09860348e1478905c731379b6cd591b43d91c3a Mon Sep 17 00:00:00 2001 From: Jacek Date: Tue, 18 Mar 2025 11:23:02 -0500 Subject: [PATCH 21/23] wip --- packages/clerk-js/src/core/resources/UserSettings.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/clerk-js/src/core/resources/UserSettings.ts b/packages/clerk-js/src/core/resources/UserSettings.ts index 92ddaab4917..1842d3166bf 100644 --- a/packages/clerk-js/src/core/resources/UserSettings.ts +++ b/packages/clerk-js/src/core/resources/UserSettings.ts @@ -175,9 +175,6 @@ export class UserSettings extends BaseResource implements UserSettingsResource { .flat() as any as Web3Strategy[]; } - /** - * Constructor now accepts an optional data object. - */ public constructor(data: UserSettingsJSON | UserSettingsJSONSnapshot | null = null) { super(); this.fromJSON(data); From 0115dcf8531de30b9c6722614ce7a240e48cd905 Mon Sep 17 00:00:00 2001 From: Jacek Radko Date: Tue, 18 Mar 2025 11:24:38 -0500 Subject: [PATCH 22/23] Apply suggestions from code review Co-authored-by: Lennart --- packages/clerk-js/src/core/resources/UserSettings.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/clerk-js/src/core/resources/UserSettings.ts b/packages/clerk-js/src/core/resources/UserSettings.ts index 1842d3166bf..152a5e28233 100644 --- a/packages/clerk-js/src/core/resources/UserSettings.ts +++ b/packages/clerk-js/src/core/resources/UserSettings.ts @@ -65,12 +65,10 @@ export class UserSettings extends BaseResource implements UserSettingsResource { }, username: { ...DISABLED_ATTRIBUTE, - name: 'username', }, web3_wallet: { ...DISABLED_ATTRIBUTE, - name: 'web3_wallet', }, first_name: { From ecf070af6f988f0d28d3db5c4f5f1dbfdc8e485e Mon Sep 17 00:00:00 2001 From: Jacek Date: Tue, 18 Mar 2025 11:30:00 -0500 Subject: [PATCH 23/23] wip --- packages/clerk-js/bundlewatch.config.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/clerk-js/bundlewatch.config.json b/packages/clerk-js/bundlewatch.config.json index 181d1c05bf9..2c56eb95669 100644 --- a/packages/clerk-js/bundlewatch.config.json +++ b/packages/clerk-js/bundlewatch.config.json @@ -1,7 +1,7 @@ { "files": [ - { "path": "./dist/clerk.js", "maxSize": "577kB" }, - { "path": "./dist/clerk.browser.js", "maxSize": "78kB" }, + { "path": "./dist/clerk.js", "maxSize": "577.5kB" }, + { "path": "./dist/clerk.browser.js", "maxSize": "78.5kB" }, { "path": "./dist/clerk.headless.js", "maxSize": "51KB" }, { "path": "./dist/ui-common*.js", "maxSize": "94KB" }, { "path": "./dist/vendors*.js", "maxSize": "30KB" },