From f84a3a1c48ad76519a5059a2c540791a8fa3df57 Mon Sep 17 00:00:00 2001 From: Laura Beatris Date: Wed, 29 Apr 2026 15:45:48 -0700 Subject: [PATCH 1/8] Add methods to manage enterprise connection --- packages/clerk-js/src/core/resources/User.ts | 46 +++++++ .../src/core/resources/__tests__/User.test.ts | 130 ++++++++++++++++++ .../src/utils/meEnterpriseConnectionBody.ts | 72 ++++++++++ .../shared/src/types/enterpriseConnection.ts | 50 +++++++ packages/shared/src/types/user.ts | 14 +- 5 files changed, 311 insertions(+), 1 deletion(-) create mode 100644 packages/clerk-js/src/utils/meEnterpriseConnectionBody.ts diff --git a/packages/clerk-js/src/core/resources/User.ts b/packages/clerk-js/src/core/resources/User.ts index 9087dcacc58..a1ca7e431a0 100644 --- a/packages/clerk-js/src/core/resources/User.ts +++ b/packages/clerk-js/src/core/resources/User.ts @@ -14,7 +14,9 @@ import type { EnterpriseConnectionResource, ExternalAccountJSON, ExternalAccountResource, + CreateMeEnterpriseConnectionParams, GetEnterpriseConnectionsParams, + UpdateMeEnterpriseConnectionParams, GetOrganizationMemberships, GetUserOrganizationInvitationsParams, GetUserOrganizationSuggestionsParams, @@ -36,6 +38,10 @@ import type { } from '@clerk/shared/types'; import { unixEpochToDate } from '../../utils/date'; +import { + buildCreateMeEnterpriseConnectionBody, + buildUpdateMeEnterpriseConnectionBody, +} from '../../utils/meEnterpriseConnectionBody'; import { normalizeUnsafeMetadata } from '../../utils/resourceParams'; import { eventBus, events } from '../events'; import { addPaymentMethod, getPaymentMethods, initializePaymentMethod } from '../modules/billing'; @@ -316,6 +322,46 @@ export class User extends BaseResource implements UserResource { return (json || []).map(connection => new EnterpriseConnection(connection)); }; + createEnterpriseConnection = async ( + params: CreateMeEnterpriseConnectionParams, + ): Promise => { + const json = ( + await BaseResource._fetch({ + path: `${this.path()}/enterprise_connections`, + method: 'POST', + body: buildCreateMeEnterpriseConnectionBody(params) as any, + }) + )?.response as unknown as EnterpriseConnectionJSON; + + return new EnterpriseConnection(json); + }; + + updateEnterpriseConnection = async ( + enterpriseConnectionId: string, + params: UpdateMeEnterpriseConnectionParams, + ): Promise => { + const json = ( + await BaseResource._fetch({ + path: `${this.path()}/enterprise_connections/${enterpriseConnectionId}`, + method: 'PATCH', + body: buildUpdateMeEnterpriseConnectionBody(params) as any, + }) + )?.response as unknown as EnterpriseConnectionJSON; + + return new EnterpriseConnection(json); + }; + + deleteEnterpriseConnection = async (enterpriseConnectionId: string): Promise => { + const json = ( + await BaseResource._fetch({ + path: `${this.path()}/enterprise_connections/${enterpriseConnectionId}`, + method: 'DELETE', + }) + )?.response as unknown as DeletedObjectJSON; + + return new DeletedObject(json); + }; + initializePaymentMethod: typeof initializePaymentMethod = params => { return initializePaymentMethod(params); }; diff --git a/packages/clerk-js/src/core/resources/__tests__/User.test.ts b/packages/clerk-js/src/core/resources/__tests__/User.test.ts index 72b5f94c86c..23f50e38276 100644 --- a/packages/clerk-js/src/core/resources/__tests__/User.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/User.test.ts @@ -139,6 +139,136 @@ describe('User', () => { expect(connections[0].allowOrganizationAccountLinking).toBe(true); }); + it('creates an enterprise connection', async () => { + const enterpriseConnectionJSON = { + id: 'ec_new', + object: 'enterprise_connection' as const, + name: 'New SSO', + active: true, + provider: 'saml_okta', + logo_public_url: null, + domains: ['acme.com'], + organization_id: null, + sync_user_attributes: true, + disable_additional_identifications: false, + allow_organization_account_linking: false, + custom_attributes: [], + oauth_config: null, + saml_connection: null, + created_at: 1234567890, + updated_at: 1234567890, + }; + + // @ts-ignore + BaseResource._fetch = vi.fn().mockReturnValue(Promise.resolve({ response: enterpriseConnectionJSON })); + + const user = new User({ + email_addresses: [], + phone_numbers: [], + web3_wallets: [], + external_accounts: [], + } as unknown as UserJSON); + + const conn = await user.createEnterpriseConnection({ + provider: 'saml_okta', + name: 'New SSO', + organizationId: 'org_1', + saml: { idpEntityId: 'https://idp.example.com' }, + }); + + // @ts-ignore + expect(BaseResource._fetch).toHaveBeenCalledWith({ + method: 'POST', + path: '/me/enterprise_connections', + body: { + provider: 'saml_okta', + name: 'New SSO', + organization_id: 'org_1', + saml: { idp_entity_id: 'https://idp.example.com' }, + }, + }); + + expect(conn.id).toBe('ec_new'); + expect(conn.name).toBe('New SSO'); + }); + + it('updates an enterprise connection', async () => { + const enterpriseConnectionJSON = { + id: 'ec_123', + object: 'enterprise_connection' as const, + name: 'Updated', + active: false, + provider: 'saml_okta', + logo_public_url: null, + domains: ['acme.com'], + organization_id: null, + sync_user_attributes: true, + disable_additional_identifications: false, + allow_organization_account_linking: false, + custom_attributes: [], + oauth_config: null, + saml_connection: null, + created_at: 1234567890, + updated_at: 1234567900, + }; + + // @ts-ignore + BaseResource._fetch = vi.fn().mockReturnValue(Promise.resolve({ response: enterpriseConnectionJSON })); + + const user = new User({ + email_addresses: [], + phone_numbers: [], + web3_wallets: [], + external_accounts: [], + } as unknown as UserJSON); + + await user.updateEnterpriseConnection('ec_123', { + name: 'Updated', + active: false, + syncUserAttributes: true, + }); + + // @ts-ignore + expect(BaseResource._fetch).toHaveBeenCalledWith({ + method: 'PATCH', + path: '/me/enterprise_connections/ec_123', + body: { + name: 'Updated', + active: false, + sync_user_attributes: true, + }, + }); + }); + + it('deletes an enterprise connection', async () => { + const deletedJSON = { + object: 'enterprise_connection', + id: 'ec_123', + deleted: true, + }; + + // @ts-ignore + BaseResource._fetch = vi.fn().mockReturnValue(Promise.resolve({ response: deletedJSON })); + + const user = new User({ + email_addresses: [], + phone_numbers: [], + web3_wallets: [], + external_accounts: [], + } as unknown as UserJSON); + + const result = await user.deleteEnterpriseConnection('ec_123'); + + // @ts-ignore + expect(BaseResource._fetch).toHaveBeenCalledWith({ + method: 'DELETE', + path: '/me/enterprise_connections/ec_123', + }); + + expect(result.id).toBe('ec_123'); + expect(result.deleted).toBe(true); + }); + it('creates a web3 wallet', async () => { const targetWeb3Wallet = '0x0000000000000000000000000000000000000000'; const web3WalletJSON = { diff --git a/packages/clerk-js/src/utils/meEnterpriseConnectionBody.ts b/packages/clerk-js/src/utils/meEnterpriseConnectionBody.ts new file mode 100644 index 00000000000..7b5c13fc417 --- /dev/null +++ b/packages/clerk-js/src/utils/meEnterpriseConnectionBody.ts @@ -0,0 +1,72 @@ +import type { + CreateMeEnterpriseConnectionParams, + MeEnterpriseConnectionOidcInput, + MeEnterpriseConnectionSamlInput, + UpdateMeEnterpriseConnectionParams, +} from '@clerk/shared/types'; + +function samlToJson(saml: MeEnterpriseConnectionSamlInput): Record { + const body: Record = {}; + if (saml.idpEntityId !== undefined) body.idp_entity_id = saml.idpEntityId; + if (saml.idpSsoUrl !== undefined) body.idp_sso_url = saml.idpSsoUrl; + if (saml.idpCertificate !== undefined) body.idp_certificate = saml.idpCertificate; + if (saml.idpMetadataUrl !== undefined) body.idp_metadata_url = saml.idpMetadataUrl; + if (saml.idpMetadata !== undefined) body.idp_metadata = saml.idpMetadata; + if (saml.attributeMapping !== undefined) body.attribute_mapping = saml.attributeMapping; + if (saml.allowSubdomains !== undefined) body.allow_subdomains = saml.allowSubdomains; + if (saml.allowIdpInitiated !== undefined) body.allow_idp_initiated = saml.allowIdpInitiated; + if (saml.forceAuthn !== undefined) body.force_authn = saml.forceAuthn; + return body; +} + +function oidcToJson(oidc: MeEnterpriseConnectionOidcInput): Record { + const body: Record = {}; + if (oidc.clientId !== undefined) body.client_id = oidc.clientId; + if (oidc.clientSecret !== undefined) body.client_secret = oidc.clientSecret; + if (oidc.discoveryUrl !== undefined) body.discovery_url = oidc.discoveryUrl; + if (oidc.authUrl !== undefined) body.auth_url = oidc.authUrl; + if (oidc.tokenUrl !== undefined) body.token_url = oidc.tokenUrl; + if (oidc.userInfoUrl !== undefined) body.user_info_url = oidc.userInfoUrl; + if (oidc.requiresPkce !== undefined) body.requires_pkce = oidc.requiresPkce; + return body; +} + +export function buildCreateMeEnterpriseConnectionBody( + params: CreateMeEnterpriseConnectionParams, +): Record { + const body: Record = { + provider: params.provider, + name: params.name, + }; + if (params.organizationId !== undefined) { + body.organization_id = params.organizationId; + } + if (params.saml !== undefined) { + body.saml = params.saml === null ? null : samlToJson(params.saml); + } + if (params.oidc !== undefined) { + body.oidc = params.oidc === null ? null : oidcToJson(params.oidc); + } + return body; +} + +export function buildUpdateMeEnterpriseConnectionBody( + params: UpdateMeEnterpriseConnectionParams, +): Record { + const body: Record = {}; + if (params.name !== undefined) body.name = params.name; + if (params.active !== undefined) body.active = params.active; + if (params.syncUserAttributes !== undefined) body.sync_user_attributes = params.syncUserAttributes; + if (params.disableAdditionalIdentifications !== undefined) { + body.disable_additional_identifications = params.disableAdditionalIdentifications; + } + if (params.organizationId !== undefined) body.organization_id = params.organizationId; + if (params.customAttributes !== undefined) body.custom_attributes = params.customAttributes; + if (params.saml !== undefined) { + body.saml = params.saml === null ? null : samlToJson(params.saml); + } + if (params.oidc !== undefined) { + body.oidc = params.oidc === null ? null : oidcToJson(params.oidc); + } + return body; +} diff --git a/packages/shared/src/types/enterpriseConnection.ts b/packages/shared/src/types/enterpriseConnection.ts index 1acf0950167..c47641f2242 100644 --- a/packages/shared/src/types/enterpriseConnection.ts +++ b/packages/shared/src/types/enterpriseConnection.ts @@ -97,3 +97,53 @@ export interface EnterpriseOAuthConfigResource { createdAt: Date | null; updatedAt: Date | null; } + +export type MeEnterpriseConnectionProvider = + | 'saml_custom' + | 'saml_okta' + | 'saml_google' + | 'saml_microsoft' + | 'oidc_custom' + | 'oidc_github_enterprise' + | 'oidc_gitlab'; + +export type MeEnterpriseConnectionSamlInput = { + idpEntityId?: string | null; + idpSsoUrl?: string | null; + idpCertificate?: string | null; + idpMetadataUrl?: string | null; + idpMetadata?: string | null; + attributeMapping?: Record | null; + allowSubdomains?: boolean | null; + allowIdpInitiated?: boolean | null; + forceAuthn?: boolean | null; +}; + +export type MeEnterpriseConnectionOidcInput = { + clientId?: string | null; + clientSecret?: string | null; + discoveryUrl?: string | null; + authUrl?: string | null; + tokenUrl?: string | null; + userInfoUrl?: string | null; + requiresPkce?: boolean | null; +}; + +export type CreateMeEnterpriseConnectionParams = { + provider: MeEnterpriseConnectionProvider; + name: string; + organizationId?: string | null; + saml?: MeEnterpriseConnectionSamlInput | null; + oidc?: MeEnterpriseConnectionOidcInput | null; +}; + +export type UpdateMeEnterpriseConnectionParams = { + name?: string | null; + active?: boolean | null; + syncUserAttributes?: boolean | null; + disableAdditionalIdentifications?: boolean | null; + organizationId?: string | null; + customAttributes?: Record | null; + saml?: MeEnterpriseConnectionSamlInput | null; + oidc?: MeEnterpriseConnectionOidcInput | null; +}; diff --git a/packages/shared/src/types/user.ts b/packages/shared/src/types/user.ts index c0217e299e4..4ea4c35be39 100644 --- a/packages/shared/src/types/user.ts +++ b/packages/shared/src/types/user.ts @@ -3,7 +3,11 @@ import type { BillingPayerMethods } from './billing'; import type { DeletedObjectResource } from './deletedObject'; import type { EmailAddressResource } from './emailAddress'; import type { EnterpriseAccountResource } from './enterpriseAccount'; -import type { EnterpriseConnectionResource } from './enterpriseConnection'; +import type { + CreateMeEnterpriseConnectionParams, + EnterpriseConnectionResource, + UpdateMeEnterpriseConnectionParams, +} from './enterpriseConnection'; import type { ExternalAccountResource } from './externalAccount'; import type { ImageResource } from './image'; import type { UserJSON } from './json'; @@ -120,6 +124,14 @@ export interface UserResource extends ClerkResource, BillingPayerMethods { getOrganizationCreationDefaults: () => Promise; leaveOrganization: (organizationId: string) => Promise; getEnterpriseConnections: (params?: GetEnterpriseConnectionsParams) => Promise; + createEnterpriseConnection: ( + params: CreateMeEnterpriseConnectionParams, + ) => Promise; + updateEnterpriseConnection: ( + enterpriseConnectionId: string, + params: UpdateMeEnterpriseConnectionParams, + ) => Promise; + deleteEnterpriseConnection: (enterpriseConnectionId: string) => Promise; createTOTP: () => Promise; verifyTOTP: (params: VerifyTOTPParams) => Promise; disableTOTP: () => Promise; From bf6496175e8d9532544fc576ab59a864c75bd572 Mon Sep 17 00:00:00 2001 From: Laura Beatris Date: Wed, 29 Apr 2026 15:55:17 -0700 Subject: [PATCH 2/8] Add enterprise connection test run resource --- .../resources/EnterpriseConnectionTestRun.ts | 172 ++++++++++++++++++ packages/clerk-js/src/core/resources/User.ts | 59 +++++- .../src/core/resources/__tests__/User.test.ts | 67 +++++++ .../clerk-js/src/core/resources/internal.ts | 1 + .../src/utils/meEnterpriseConnectionBody.ts | 72 -------- .../src/types/enterpriseConnectionTestRun.ts | 99 ++++++++++ packages/shared/src/types/index.ts | 1 + packages/shared/src/types/user.ts | 16 +- 8 files changed, 406 insertions(+), 81 deletions(-) create mode 100644 packages/clerk-js/src/core/resources/EnterpriseConnectionTestRun.ts delete mode 100644 packages/clerk-js/src/utils/meEnterpriseConnectionBody.ts create mode 100644 packages/shared/src/types/enterpriseConnectionTestRun.ts diff --git a/packages/clerk-js/src/core/resources/EnterpriseConnectionTestRun.ts b/packages/clerk-js/src/core/resources/EnterpriseConnectionTestRun.ts new file mode 100644 index 00000000000..b930dc3dae5 --- /dev/null +++ b/packages/clerk-js/src/core/resources/EnterpriseConnectionTestRun.ts @@ -0,0 +1,172 @@ +import type { + ClerkResourceReloadParams, + EnterpriseConnectionTestRunJSON, + EnterpriseConnectionTestRunJSONSnapshot, + EnterpriseConnectionTestRunLogResource, + EnterpriseConnectionTestRunOauthPayloadJSON, + EnterpriseConnectionTestRunOauthPayloadResource, + EnterpriseConnectionTestRunParsedUserInfoJSON, + EnterpriseConnectionTestRunParsedUserInfoResource, + EnterpriseConnectionTestRunResource, + EnterpriseConnectionTestRunSamlPayloadJSON, + EnterpriseConnectionTestRunSamlPayloadResource, +} from '@clerk/shared/types'; + +import { unixEpochToDate } from '../../utils/date'; +import { clerkUnsupportedReloadMethod } from '../errors'; +import { BaseResource } from './Base'; + +export class EnterpriseConnectionTestRun extends BaseResource implements EnterpriseConnectionTestRunResource { + pathRoot = '/me'; + private enterpriseConnectionId = ''; + + id!: string; + status!: string; + connectionType!: 'saml' | 'oauth'; + parsedUserInfo: EnterpriseConnectionTestRunParsedUserInfoResource | null = null; + logs: EnterpriseConnectionTestRunLogResource[] = []; + saml: EnterpriseConnectionTestRunSamlPayloadResource | null = null; + oauth: EnterpriseConnectionTestRunOauthPayloadResource | null = null; + createdAt: Date | null = null; + + constructor(data: EnterpriseConnectionTestRunJSON, enterpriseConnectionId: string) { + super(); + this.enterpriseConnectionId = enterpriseConnectionId; + this.fromJSON(data); + } + + protected path(): string { + return `${this.pathRoot}/enterprise_connections/${this.enterpriseConnectionId}/test_runs/${this.id}`; + } + + reload(_?: ClerkResourceReloadParams): Promise { + clerkUnsupportedReloadMethod('EnterpriseConnectionTestRun'); + } + + protected fromJSON(data: EnterpriseConnectionTestRunJSON | null): this { + if (!data) { + return this; + } + + this.id = data.id; + this.status = data.status; + this.connectionType = data.connection_type; + this.parsedUserInfo = parsedUserInfoFromJSON(data.parsed_user_info ?? null); + this.saml = samlPayloadFromJSON(data.saml ?? null); + this.oauth = oauthPayloadFromJSON(data.oauth ?? null); + this.createdAt = unixEpochToDate(data.created_at); + this.logs = (data.logs ?? []).map(log => ({ + level: log.level, + code: log.code, + shortMessage: log.short_message, + message: log.message, + })); + + return this; + } + + public __internal_toSnapshot(): EnterpriseConnectionTestRunJSONSnapshot { + return { + object: 'enterprise_connection_test_run', + id: this.id, + status: this.status, + connection_type: this.connectionType, + parsed_user_info: parsedUserInfoToJSON(this.parsedUserInfo), + saml: samlPayloadToJSON(this.saml), + oauth: oauthPayloadToJSON(this.oauth), + logs: this.logs.map(log => ({ + level: log.level, + code: log.code, + short_message: log.shortMessage, + message: log.message, + })), + created_at: this.createdAt?.getTime() ?? 0, + }; + } +} + +function parsedUserInfoFromJSON( + data: EnterpriseConnectionTestRunParsedUserInfoJSON | null | undefined, +): EnterpriseConnectionTestRunParsedUserInfoResource | null { + if (!data) { + return null; + } + + return { + emailAddress: data.email_address, + firstName: data.first_name, + lastName: data.last_name, + userId: data.user_id, + }; +} + +function parsedUserInfoToJSON( + data: EnterpriseConnectionTestRunParsedUserInfoResource | null, +): EnterpriseConnectionTestRunParsedUserInfoJSON | null { + if (!data) { + return null; + } + + return { + email_address: data.emailAddress, + first_name: data.firstName, + last_name: data.lastName, + user_id: data.userId, + }; +} + +function samlPayloadFromJSON( + data: EnterpriseConnectionTestRunSamlPayloadJSON | null | undefined, +): EnterpriseConnectionTestRunSamlPayloadResource | null { + if (!data) { + return null; + } + + return { + samlRequest: data.saml_request, + samlResponse: data.saml_response, + relayState: data.relay_state, + }; +} + +function samlPayloadToJSON( + data: EnterpriseConnectionTestRunSamlPayloadResource | null, +): EnterpriseConnectionTestRunSamlPayloadJSON | null { + if (!data) { + return null; + } + + return { + saml_request: data.samlRequest, + saml_response: data.samlResponse, + relay_state: data.relayState, + }; +} + +function oauthPayloadFromJSON( + data: EnterpriseConnectionTestRunOauthPayloadJSON | null | undefined, +): EnterpriseConnectionTestRunOauthPayloadResource | null { + if (!data) { + return null; + } + + return { + idToken: data.id_token, + accessToken: data.access_token, + userInfo: data.user_info, + }; +} + +function oauthPayloadToJSON( + data: EnterpriseConnectionTestRunOauthPayloadResource | null, +): EnterpriseConnectionTestRunOauthPayloadJSON | null { + if (!data) { + return null; + } + + return { + id_token: data.idToken, + access_token: data.accessToken, + user_info: data.userInfo, + }; +} diff --git a/packages/clerk-js/src/core/resources/User.ts b/packages/clerk-js/src/core/resources/User.ts index a1ca7e431a0..d1cc5b3e5a5 100644 --- a/packages/clerk-js/src/core/resources/User.ts +++ b/packages/clerk-js/src/core/resources/User.ts @@ -1,4 +1,5 @@ import { getFullName } from '@clerk/shared/internal/clerk-js/user'; +import { deepCamelToSnake } from '@clerk/shared/underscore'; import type { BackupCodeJSON, BackupCodeResource, @@ -14,7 +15,14 @@ import type { EnterpriseConnectionResource, ExternalAccountJSON, ExternalAccountResource, + ClerkPaginatedResponse, CreateMeEnterpriseConnectionParams, + EnterpriseConnectionTestRunInitJSON, + EnterpriseConnectionTestRunJSON, + EnterpriseConnectionTestRunsPaginatedJSON, + EnterpriseConnectionTestRunInitResource, + EnterpriseConnectionTestRunResource, + GetEnterpriseConnectionTestRunsParams, GetEnterpriseConnectionsParams, UpdateMeEnterpriseConnectionParams, GetOrganizationMemberships, @@ -38,10 +46,7 @@ import type { } from '@clerk/shared/types'; import { unixEpochToDate } from '../../utils/date'; -import { - buildCreateMeEnterpriseConnectionBody, - buildUpdateMeEnterpriseConnectionBody, -} from '../../utils/meEnterpriseConnectionBody'; +import { convertPageToOffsetSearchParams } from '../../utils/convertPageToOffsetSearchParams'; import { normalizeUnsafeMetadata } from '../../utils/resourceParams'; import { eventBus, events } from '../events'; import { addPaymentMethod, getPaymentMethods, initializePaymentMethod } from '../modules/billing'; @@ -52,6 +57,7 @@ import { EmailAddress, EnterpriseAccount, EnterpriseConnection, + EnterpriseConnectionTestRun, ExternalAccount, Image, OrganizationMembership, @@ -329,7 +335,7 @@ export class User extends BaseResource implements UserResource { await BaseResource._fetch({ path: `${this.path()}/enterprise_connections`, method: 'POST', - body: buildCreateMeEnterpriseConnectionBody(params) as any, + body: deepCamelToSnake(params) as any, }) )?.response as unknown as EnterpriseConnectionJSON; @@ -344,7 +350,7 @@ export class User extends BaseResource implements UserResource { await BaseResource._fetch({ path: `${this.path()}/enterprise_connections/${enterpriseConnectionId}`, method: 'PATCH', - body: buildUpdateMeEnterpriseConnectionBody(params) as any, + body: deepCamelToSnake(params) as any, }) )?.response as unknown as EnterpriseConnectionJSON; @@ -362,6 +368,47 @@ export class User extends BaseResource implements UserResource { return new DeletedObject(json); }; + createEnterpriseConnectionTestRun = async ( + enterpriseConnectionId: string, + ): Promise => { + const json = ( + await BaseResource._fetch({ + path: `${this.path()}/enterprise_connections/${enterpriseConnectionId}/test_runs`, + method: 'POST', + }) + )?.response as unknown as EnterpriseConnectionTestRunInitJSON; + + return { url: json.url }; + }; + + getEnterpriseConnectionTestRuns = async ( + enterpriseConnectionId: string, + params?: GetEnterpriseConnectionTestRunsParams, + ): Promise> => { + const { status, ...rest } = params || {}; + const search = convertPageToOffsetSearchParams({ ...rest, paginated: true }); + if (status?.length) { + for (const s of status) { + search.append('status', s); + } + } + + const res = await BaseResource._fetch({ + path: `${this.path()}/enterprise_connections/${enterpriseConnectionId}/test_runs`, + method: 'GET', + search, + }); + + const payload = res?.response as unknown as EnterpriseConnectionTestRunsPaginatedJSON | undefined; + + return { + total_count: payload?.total_count ?? 0, + data: (payload?.data ?? []).map( + (row: EnterpriseConnectionTestRunJSON) => new EnterpriseConnectionTestRun(row, enterpriseConnectionId), + ), + }; + }; + initializePaymentMethod: typeof initializePaymentMethod = params => { return initializePaymentMethod(params); }; diff --git a/packages/clerk-js/src/core/resources/__tests__/User.test.ts b/packages/clerk-js/src/core/resources/__tests__/User.test.ts index 23f50e38276..7665e5165e3 100644 --- a/packages/clerk-js/src/core/resources/__tests__/User.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/User.test.ts @@ -269,6 +269,73 @@ describe('User', () => { expect(result.deleted).toBe(true); }); + it('creates an enterprise connection test run', async () => { + // @ts-ignore + BaseResource._fetch = vi.fn().mockReturnValue(Promise.resolve({ response: { url: 'https://example.com/test' } })); + + const user = new User({ + email_addresses: [], + phone_numbers: [], + web3_wallets: [], + external_accounts: [], + } as unknown as UserJSON); + + const init = await user.createEnterpriseConnectionTestRun('ec_123'); + + // @ts-ignore + expect(BaseResource._fetch).toHaveBeenCalledWith({ + method: 'POST', + path: '/me/enterprise_connections/ec_123/test_runs', + }); + + expect(init.url).toBe('https://example.com/test'); + }); + + it('lists enterprise connection test runs', async () => { + const paginated = { + data: [ + { + object: 'enterprise_connection_test_run' as const, + id: 'run_1', + status: 'success', + connection_type: 'saml' as const, + created_at: 1700000000000, + }, + ], + total_count: 1, + }; + + // @ts-ignore + BaseResource._fetch = vi.fn().mockReturnValue(Promise.resolve({ response: paginated })); + + const user = new User({ + email_addresses: [], + phone_numbers: [], + web3_wallets: [], + external_accounts: [], + } as unknown as UserJSON); + + const result = await user.getEnterpriseConnectionTestRuns('ec_123', { + initialPage: 1, + pageSize: 10, + status: ['pending', 'success'], + }); + + // @ts-ignore + const call = BaseResource._fetch.mock.calls[0][0]; + expect(call.method).toBe('GET'); + expect(call.path).toBe('/me/enterprise_connections/ec_123/test_runs'); + expect(call.search.get('limit')).toBe('10'); + expect(call.search.get('offset')).toBe('0'); + expect(call.search.get('paginated')).toBe('true'); + expect(call.search.getAll('status')).toEqual(['pending', 'success']); + + expect(result.total_count).toBe(1); + expect(result.data).toHaveLength(1); + expect(result.data[0].id).toBe('run_1'); + expect(result.data[0].connectionType).toBe('saml'); + }); + it('creates a web3 wallet', async () => { const targetWeb3Wallet = '0x0000000000000000000000000000000000000000'; const web3WalletJSON = { diff --git a/packages/clerk-js/src/core/resources/internal.ts b/packages/clerk-js/src/core/resources/internal.ts index 0cdb99971d1..9ac3efbd232 100644 --- a/packages/clerk-js/src/core/resources/internal.ts +++ b/packages/clerk-js/src/core/resources/internal.ts @@ -17,6 +17,7 @@ export * from './DisplayConfig'; export * from './EmailAddress'; export * from './EnterpriseAccount'; export * from './EnterpriseConnection'; +export * from './EnterpriseConnectionTestRun'; export * from './Environment'; export * from './ExternalAccount'; export * from './Feature'; diff --git a/packages/clerk-js/src/utils/meEnterpriseConnectionBody.ts b/packages/clerk-js/src/utils/meEnterpriseConnectionBody.ts deleted file mode 100644 index 7b5c13fc417..00000000000 --- a/packages/clerk-js/src/utils/meEnterpriseConnectionBody.ts +++ /dev/null @@ -1,72 +0,0 @@ -import type { - CreateMeEnterpriseConnectionParams, - MeEnterpriseConnectionOidcInput, - MeEnterpriseConnectionSamlInput, - UpdateMeEnterpriseConnectionParams, -} from '@clerk/shared/types'; - -function samlToJson(saml: MeEnterpriseConnectionSamlInput): Record { - const body: Record = {}; - if (saml.idpEntityId !== undefined) body.idp_entity_id = saml.idpEntityId; - if (saml.idpSsoUrl !== undefined) body.idp_sso_url = saml.idpSsoUrl; - if (saml.idpCertificate !== undefined) body.idp_certificate = saml.idpCertificate; - if (saml.idpMetadataUrl !== undefined) body.idp_metadata_url = saml.idpMetadataUrl; - if (saml.idpMetadata !== undefined) body.idp_metadata = saml.idpMetadata; - if (saml.attributeMapping !== undefined) body.attribute_mapping = saml.attributeMapping; - if (saml.allowSubdomains !== undefined) body.allow_subdomains = saml.allowSubdomains; - if (saml.allowIdpInitiated !== undefined) body.allow_idp_initiated = saml.allowIdpInitiated; - if (saml.forceAuthn !== undefined) body.force_authn = saml.forceAuthn; - return body; -} - -function oidcToJson(oidc: MeEnterpriseConnectionOidcInput): Record { - const body: Record = {}; - if (oidc.clientId !== undefined) body.client_id = oidc.clientId; - if (oidc.clientSecret !== undefined) body.client_secret = oidc.clientSecret; - if (oidc.discoveryUrl !== undefined) body.discovery_url = oidc.discoveryUrl; - if (oidc.authUrl !== undefined) body.auth_url = oidc.authUrl; - if (oidc.tokenUrl !== undefined) body.token_url = oidc.tokenUrl; - if (oidc.userInfoUrl !== undefined) body.user_info_url = oidc.userInfoUrl; - if (oidc.requiresPkce !== undefined) body.requires_pkce = oidc.requiresPkce; - return body; -} - -export function buildCreateMeEnterpriseConnectionBody( - params: CreateMeEnterpriseConnectionParams, -): Record { - const body: Record = { - provider: params.provider, - name: params.name, - }; - if (params.organizationId !== undefined) { - body.organization_id = params.organizationId; - } - if (params.saml !== undefined) { - body.saml = params.saml === null ? null : samlToJson(params.saml); - } - if (params.oidc !== undefined) { - body.oidc = params.oidc === null ? null : oidcToJson(params.oidc); - } - return body; -} - -export function buildUpdateMeEnterpriseConnectionBody( - params: UpdateMeEnterpriseConnectionParams, -): Record { - const body: Record = {}; - if (params.name !== undefined) body.name = params.name; - if (params.active !== undefined) body.active = params.active; - if (params.syncUserAttributes !== undefined) body.sync_user_attributes = params.syncUserAttributes; - if (params.disableAdditionalIdentifications !== undefined) { - body.disable_additional_identifications = params.disableAdditionalIdentifications; - } - if (params.organizationId !== undefined) body.organization_id = params.organizationId; - if (params.customAttributes !== undefined) body.custom_attributes = params.customAttributes; - if (params.saml !== undefined) { - body.saml = params.saml === null ? null : samlToJson(params.saml); - } - if (params.oidc !== undefined) { - body.oidc = params.oidc === null ? null : oidcToJson(params.oidc); - } - return body; -} diff --git a/packages/shared/src/types/enterpriseConnectionTestRun.ts b/packages/shared/src/types/enterpriseConnectionTestRun.ts new file mode 100644 index 00000000000..5fb46792cc0 --- /dev/null +++ b/packages/shared/src/types/enterpriseConnectionTestRun.ts @@ -0,0 +1,99 @@ +import type { ClerkResourceJSON } from './json'; +import type { ClerkPaginationParams } from './pagination'; +import type { ClerkResource } from './resource'; + +export interface EnterpriseConnectionTestRunInitJSON { + url: string; +} + +export interface EnterpriseConnectionTestRunInitResource { + url: string; +} + +export type EnterpriseConnectionTestRunStatus = 'pending' | 'success' | 'failed'; + +export interface EnterpriseConnectionTestRunParsedUserInfoJSON { + email_address?: string; + first_name?: string; + last_name?: string; + user_id?: string; +} + +export interface EnterpriseConnectionTestRunLogJSON { + level?: string; + code?: string; + short_message?: string; + message?: string; +} + +export interface EnterpriseConnectionTestRunSamlPayloadJSON { + saml_request?: string; + saml_response?: string; + relay_state?: string; +} + +export interface EnterpriseConnectionTestRunOauthPayloadJSON { + id_token?: string; + access_token?: string; + user_info?: string; +} + +export interface EnterpriseConnectionTestRunJSON extends ClerkResourceJSON { + object: 'enterprise_connection_test_run'; + status: string; + connection_type: 'saml' | 'oauth'; + parsed_user_info?: EnterpriseConnectionTestRunParsedUserInfoJSON | null; + logs?: EnterpriseConnectionTestRunLogJSON[]; + saml?: EnterpriseConnectionTestRunSamlPayloadJSON | null; + oauth?: EnterpriseConnectionTestRunOauthPayloadJSON | null; + created_at: number; +} + +export type EnterpriseConnectionTestRunJSONSnapshot = EnterpriseConnectionTestRunJSON; + +export interface EnterpriseConnectionTestRunParsedUserInfoResource { + emailAddress?: string; + firstName?: string; + lastName?: string; + userId?: string; +} + +export interface EnterpriseConnectionTestRunLogResource { + level?: string; + code?: string; + shortMessage?: string; + message?: string; +} + +export interface EnterpriseConnectionTestRunSamlPayloadResource { + samlRequest?: string; + samlResponse?: string; + relayState?: string; +} + +export interface EnterpriseConnectionTestRunOauthPayloadResource { + idToken?: string; + accessToken?: string; + userInfo?: string; +} + +export interface EnterpriseConnectionTestRunResource extends ClerkResource { + id: string; + status: string; + connectionType: 'saml' | 'oauth'; + parsedUserInfo: EnterpriseConnectionTestRunParsedUserInfoResource | null; + logs: EnterpriseConnectionTestRunLogResource[]; + saml: EnterpriseConnectionTestRunSamlPayloadResource | null; + oauth: EnterpriseConnectionTestRunOauthPayloadResource | null; + createdAt: Date | null; + __internal_toSnapshot: () => EnterpriseConnectionTestRunJSONSnapshot; +} + +export type EnterpriseConnectionTestRunsPaginatedJSON = { + data: EnterpriseConnectionTestRunJSON[]; + total_count: number; +}; + +export type GetEnterpriseConnectionTestRunsParams = ClerkPaginationParams<{ + status?: EnterpriseConnectionTestRunStatus[]; +}>; diff --git a/packages/shared/src/types/index.ts b/packages/shared/src/types/index.ts index 2849a74a140..7ab38b098d1 100644 --- a/packages/shared/src/types/index.ts +++ b/packages/shared/src/types/index.ts @@ -17,6 +17,7 @@ export type * from './elementIds'; export type * from './emailAddress'; export type * from './enterpriseAccount'; export type * from './enterpriseConnection'; +export type * from './enterpriseConnectionTestRun'; export type * from './environment'; export type * from './errors'; export type * from './externalAccount'; diff --git a/packages/shared/src/types/user.ts b/packages/shared/src/types/user.ts index 4ea4c35be39..43e5aa0a492 100644 --- a/packages/shared/src/types/user.ts +++ b/packages/shared/src/types/user.ts @@ -8,6 +8,11 @@ import type { EnterpriseConnectionResource, UpdateMeEnterpriseConnectionParams, } from './enterpriseConnection'; +import type { + EnterpriseConnectionTestRunInitResource, + EnterpriseConnectionTestRunResource, + GetEnterpriseConnectionTestRunsParams, +} from './enterpriseConnectionTestRun'; import type { ExternalAccountResource } from './externalAccount'; import type { ImageResource } from './image'; import type { UserJSON } from './json'; @@ -124,14 +129,19 @@ export interface UserResource extends ClerkResource, BillingPayerMethods { getOrganizationCreationDefaults: () => Promise; leaveOrganization: (organizationId: string) => Promise; getEnterpriseConnections: (params?: GetEnterpriseConnectionsParams) => Promise; - createEnterpriseConnection: ( - params: CreateMeEnterpriseConnectionParams, - ) => Promise; + createEnterpriseConnection: (params: CreateMeEnterpriseConnectionParams) => Promise; updateEnterpriseConnection: ( enterpriseConnectionId: string, params: UpdateMeEnterpriseConnectionParams, ) => Promise; deleteEnterpriseConnection: (enterpriseConnectionId: string) => Promise; + createEnterpriseConnectionTestRun: ( + enterpriseConnectionId: string, + ) => Promise; + getEnterpriseConnectionTestRuns: ( + enterpriseConnectionId: string, + params?: GetEnterpriseConnectionTestRunsParams, + ) => Promise>; createTOTP: () => Promise; verifyTOTP: (params: VerifyTOTPParams) => Promise; disableTOTP: () => Promise; From 36f790ec10cd407f8888a3bea70fb924a7e866cf Mon Sep 17 00:00:00 2001 From: Laura Beatris Date: Wed, 29 Apr 2026 16:47:16 -0700 Subject: [PATCH 3/8] Update `useUserEnterpriseConnections` to have mutation methods --- .../src/core/resources/__tests__/User.test.ts | 1 - .../hooks/useUserEnterpriseConnections.tsx | 56 ++++++++++++++++++- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/packages/clerk-js/src/core/resources/__tests__/User.test.ts b/packages/clerk-js/src/core/resources/__tests__/User.test.ts index 7665e5165e3..ba2c814cc68 100644 --- a/packages/clerk-js/src/core/resources/__tests__/User.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/User.test.ts @@ -327,7 +327,6 @@ describe('User', () => { expect(call.path).toBe('/me/enterprise_connections/ec_123/test_runs'); expect(call.search.get('limit')).toBe('10'); expect(call.search.get('offset')).toBe('0'); - expect(call.search.get('paginated')).toBe('true'); expect(call.search.getAll('status')).toEqual(['pending', 'success']); expect(result.total_count).toBe(1); diff --git a/packages/shared/src/react/hooks/useUserEnterpriseConnections.tsx b/packages/shared/src/react/hooks/useUserEnterpriseConnections.tsx index 6f4c1c396f0..101224a0ceb 100644 --- a/packages/shared/src/react/hooks/useUserEnterpriseConnections.tsx +++ b/packages/shared/src/react/hooks/useUserEnterpriseConnections.tsx @@ -1,5 +1,13 @@ -import type { EnterpriseConnectionResource } from '../../types/enterpriseConnection'; +import { useCallback } from 'react'; + +import type { DeletedObjectResource } from '../../types/deletedObject'; +import type { + CreateMeEnterpriseConnectionParams, + EnterpriseConnectionResource, + UpdateMeEnterpriseConnectionParams, +} from '../../types/enterpriseConnection'; import { defineKeepPreviousDataFn } from '../clerk-rq/keep-previous-data'; +import { useClerkQueryClient } from '../clerk-rq/use-clerk-query-client'; import { useClerkQuery } from '../clerk-rq/useQuery'; import { useClerkInstanceContext } from '../contexts'; import { useUserBase } from './base/useUserBase'; @@ -17,6 +25,15 @@ export type UseUserEnterpriseConnectionsReturn = { error: Error | null; isLoading: boolean; isFetching: boolean; + createEnterpriseConnection: ( + params: CreateMeEnterpriseConnectionParams, + ) => Promise; + updateEnterpriseConnection: ( + enterpriseConnectionId: string, + params: UpdateMeEnterpriseConnectionParams, + ) => Promise; + deleteEnterpriseConnection: (enterpriseConnectionId: string) => Promise; + revalidate: () => Promise; }; /** @@ -30,6 +47,7 @@ function useUserEnterpriseConnections( const { keepPreviousData = true, enabled = true, withOrganizationAccountLinking = false } = params; const clerk = useClerkInstanceContext(); const user = useUserBase(); + const [queryClient] = useClerkQueryClient(); const { queryKey, stableKey, authenticated } = useUserEnterpriseConnectionsCacheKeys({ userId: user?.id ?? null, @@ -51,11 +69,47 @@ function useUserEnterpriseConnections( placeholderData: defineKeepPreviousDataFn(keepPreviousData), }); + const revalidate = useCallback( + () => queryClient.invalidateQueries({ queryKey: [stableKey] }), + [queryClient, stableKey], + ); + + const createEnterpriseConnection = useCallback( + async (createParams: CreateMeEnterpriseConnectionParams) => { + const created = await user?.createEnterpriseConnection(createParams); + await revalidate(); + return created; + }, + [user, revalidate], + ); + + const updateEnterpriseConnection = useCallback( + async (enterpriseConnectionId: string, updateParams: UpdateMeEnterpriseConnectionParams) => { + const updated = await user?.updateEnterpriseConnection(enterpriseConnectionId, updateParams); + await revalidate(); + return updated; + }, + [user, revalidate], + ); + + const deleteEnterpriseConnection = useCallback( + async (enterpriseConnectionId: string) => { + const deleted = await user?.deleteEnterpriseConnection(enterpriseConnectionId); + await revalidate(); + return deleted; + }, + [user, revalidate], + ); + return { data: query.data, error: query.error ?? null, isLoading: query.isLoading, isFetching: query.isFetching, + createEnterpriseConnection, + updateEnterpriseConnection, + deleteEnterpriseConnection, + revalidate, }; } From 5e49a5b8196c595ce110dd1c584cce9d50a96bcf Mon Sep 17 00:00:00 2001 From: Laura Beatris Date: Wed, 29 Apr 2026 17:59:47 -0700 Subject: [PATCH 4/8] Add changeset --- .changeset/pink-dolls-rush.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/pink-dolls-rush.md diff --git a/.changeset/pink-dolls-rush.md b/.changeset/pink-dolls-rush.md new file mode 100644 index 00000000000..5503d75d247 --- /dev/null +++ b/.changeset/pink-dolls-rush.md @@ -0,0 +1,6 @@ +--- +'@clerk/clerk-js': minor +'@clerk/shared': minor +--- + +Add internal API methods to manage enterprise connections From bce03e352226dc586a1c4d600abf04f420a5fbab Mon Sep 17 00:00:00 2001 From: Laura Beatris Date: Thu, 30 Apr 2026 15:03:21 -0700 Subject: [PATCH 5/8] Preserve `custom_attributes` and add unit tests --- packages/clerk-js/src/core/resources/User.ts | 31 ++++- .../src/core/resources/__tests__/User.test.ts | 129 ++++++++++++++++++ 2 files changed, 158 insertions(+), 2 deletions(-) diff --git a/packages/clerk-js/src/core/resources/User.ts b/packages/clerk-js/src/core/resources/User.ts index d1cc5b3e5a5..b99314e5f6f 100644 --- a/packages/clerk-js/src/core/resources/User.ts +++ b/packages/clerk-js/src/core/resources/User.ts @@ -335,7 +335,7 @@ export class User extends BaseResource implements UserResource { await BaseResource._fetch({ path: `${this.path()}/enterprise_connections`, method: 'POST', - body: deepCamelToSnake(params) as any, + body: toMeEnterpriseConnectionBody(params) as any, }) )?.response as unknown as EnterpriseConnectionJSON; @@ -350,7 +350,7 @@ export class User extends BaseResource implements UserResource { await BaseResource._fetch({ path: `${this.path()}/enterprise_connections/${enterpriseConnectionId}`, method: 'PATCH', - body: deepCamelToSnake(params) as any, + body: toMeEnterpriseConnectionBody(params) as any, }) )?.response as unknown as EnterpriseConnectionJSON; @@ -548,3 +548,30 @@ export class User extends BaseResource implements UserResource { }; } } + +/** + * Serializes `CreateMeEnterpriseConnectionParams` / `UpdateMeEnterpriseConnectionParams` + * for the `/me/enterprise_connections` FAPI endpoints. + * + * Uses `deepCamelToSnake` but preserves `saml.attributeMapping` and `customAttributes` as-is. Their keys are + * user-supplied data and must not be camel→snake transformed. + */ +function toMeEnterpriseConnectionBody( + params: CreateMeEnterpriseConnectionParams | UpdateMeEnterpriseConnectionParams, +): Record { + const originalAttributeMapping = + params.saml && typeof params.saml === 'object' ? params.saml.attributeMapping : undefined; + const originalCustomAttributes = 'customAttributes' in params ? params.customAttributes : undefined; + + const body = deepCamelToSnake(params) as Record; + + if (originalAttributeMapping !== undefined && body.saml && typeof body.saml === 'object') { + body.saml.attribute_mapping = originalAttributeMapping; + } + + if (originalCustomAttributes !== undefined) { + body.custom_attributes = originalCustomAttributes; + } + + return body; +} diff --git a/packages/clerk-js/src/core/resources/__tests__/User.test.ts b/packages/clerk-js/src/core/resources/__tests__/User.test.ts index ba2c814cc68..0dad85bc27e 100644 --- a/packages/clerk-js/src/core/resources/__tests__/User.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/User.test.ts @@ -240,6 +240,135 @@ describe('User', () => { }); }); + it('preserves `saml.attributeMapping` and `saml.customAttributes` keys when creating an enterprise connection', async () => { + BaseResource._fetch = vi.fn().mockReturnValue( + Promise.resolve({ + response: { + id: 'ec_new', + object: 'enterprise_connection' as const, + name: 'New SSO', + active: true, + provider: 'saml_okta', + logo_public_url: null, + domains: [], + organization_id: null, + sync_user_attributes: true, + disable_additional_identifications: false, + allow_organization_account_linking: false, + custom_attributes: [], + oauth_config: null, + saml_connection: null, + created_at: 1, + updated_at: 1, + }, + }), + ); + + const user = new User({ + email_addresses: [], + phone_numbers: [], + web3_wallets: [], + external_accounts: [], + } as unknown as UserJSON); + + await user.createEnterpriseConnection({ + provider: 'saml_okta', + name: 'New SSO', + saml: { + idpEntityId: 'https://idp.example.com', + attributeMapping: { + emailAddress: 'mail', + firstName: 'givenName', + 'custom:role': 'role', + }, + }, + }); + + // @ts-ignore + expect(BaseResource._fetch).toHaveBeenCalledWith({ + method: 'POST', + path: '/me/enterprise_connections', + body: { + provider: 'saml_okta', + name: 'New SSO', + saml: { + idp_entity_id: 'https://idp.example.com', + attribute_mapping: { + emailAddress: 'mail', + firstName: 'givenName', + 'custom:role': 'role', + }, + }, + }, + }); + }); + + it('preserves `customAttributes` and `saml.attributeMapping` keys when updating an enterprise connection', async () => { + // @ts-ignore + BaseResource._fetch = vi.fn().mockReturnValue( + Promise.resolve({ + response: { + id: 'ec_123', + object: 'enterprise_connection' as const, + name: 'Updated', + active: true, + provider: 'saml_okta', + logo_public_url: null, + domains: [], + organization_id: null, + sync_user_attributes: true, + disable_additional_identifications: false, + allow_organization_account_linking: false, + custom_attributes: [], + oauth_config: null, + saml_connection: null, + created_at: 1, + updated_at: 2, + }, + }), + ); + + const user = new User({ + email_addresses: [], + phone_numbers: [], + web3_wallets: [], + external_accounts: [], + } as unknown as UserJSON); + + await user.updateEnterpriseConnection('ec_123', { + customAttributes: { + MyClaim: 'x', + CustomValue: 'y', + nestedCamelKey: { innerCamelKey: 'z' }, + }, + saml: { + attributeMapping: { + emailAddress: 'mail', + firstName: 'givenName', + }, + }, + }); + + // @ts-ignore + expect(BaseResource._fetch).toHaveBeenCalledWith({ + method: 'PATCH', + path: '/me/enterprise_connections/ec_123', + body: { + custom_attributes: { + MyClaim: 'x', + CustomValue: 'y', + nestedCamelKey: { innerCamelKey: 'z' }, + }, + saml: { + attribute_mapping: { + emailAddress: 'mail', + firstName: 'givenName', + }, + }, + }, + }); + }); + it('deletes an enterprise connection', async () => { const deletedJSON = { object: 'enterprise_connection', From d0dafb1890971c433335243b3b20364ce9cc26ff Mon Sep 17 00:00:00 2001 From: Laura Beatris Date: Thu, 30 Apr 2026 15:11:03 -0700 Subject: [PATCH 6/8] Remove unused `paginated` param --- packages/clerk-js/src/core/resources/User.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/clerk-js/src/core/resources/User.ts b/packages/clerk-js/src/core/resources/User.ts index b99314e5f6f..06239d4042c 100644 --- a/packages/clerk-js/src/core/resources/User.ts +++ b/packages/clerk-js/src/core/resources/User.ts @@ -386,7 +386,7 @@ export class User extends BaseResource implements UserResource { params?: GetEnterpriseConnectionTestRunsParams, ): Promise> => { const { status, ...rest } = params || {}; - const search = convertPageToOffsetSearchParams({ ...rest, paginated: true }); + const search = convertPageToOffsetSearchParams(rest); if (status?.length) { for (const s of status) { search.append('status', s); From 22f775ff8adadcd4f2aed733661a587b3d1f6559 Mon Sep 17 00:00:00 2001 From: Laura Beatris Date: Thu, 30 Apr 2026 15:19:33 -0700 Subject: [PATCH 7/8] Simplify `EnterpriseConnectionTestRun` resource --- .../core/resources/EnterpriseConnectionTestRun.ts | 14 +++----------- packages/clerk-js/src/core/resources/User.ts | 4 +--- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/packages/clerk-js/src/core/resources/EnterpriseConnectionTestRun.ts b/packages/clerk-js/src/core/resources/EnterpriseConnectionTestRun.ts index b930dc3dae5..94713414487 100644 --- a/packages/clerk-js/src/core/resources/EnterpriseConnectionTestRun.ts +++ b/packages/clerk-js/src/core/resources/EnterpriseConnectionTestRun.ts @@ -14,11 +14,9 @@ import type { import { unixEpochToDate } from '../../utils/date'; import { clerkUnsupportedReloadMethod } from '../errors'; -import { BaseResource } from './Base'; -export class EnterpriseConnectionTestRun extends BaseResource implements EnterpriseConnectionTestRunResource { +export class EnterpriseConnectionTestRun implements EnterpriseConnectionTestRunResource { pathRoot = '/me'; - private enterpriseConnectionId = ''; id!: string; status!: string; @@ -29,21 +27,15 @@ export class EnterpriseConnectionTestRun extends BaseResource implements Enterpr oauth: EnterpriseConnectionTestRunOauthPayloadResource | null = null; createdAt: Date | null = null; - constructor(data: EnterpriseConnectionTestRunJSON, enterpriseConnectionId: string) { - super(); - this.enterpriseConnectionId = enterpriseConnectionId; + constructor(data: EnterpriseConnectionTestRunJSON) { this.fromJSON(data); } - protected path(): string { - return `${this.pathRoot}/enterprise_connections/${this.enterpriseConnectionId}/test_runs/${this.id}`; - } - reload(_?: ClerkResourceReloadParams): Promise { clerkUnsupportedReloadMethod('EnterpriseConnectionTestRun'); } - protected fromJSON(data: EnterpriseConnectionTestRunJSON | null): this { + private fromJSON(data: EnterpriseConnectionTestRunJSON | null): this { if (!data) { return this; } diff --git a/packages/clerk-js/src/core/resources/User.ts b/packages/clerk-js/src/core/resources/User.ts index 06239d4042c..81866cc5ede 100644 --- a/packages/clerk-js/src/core/resources/User.ts +++ b/packages/clerk-js/src/core/resources/User.ts @@ -403,9 +403,7 @@ export class User extends BaseResource implements UserResource { return { total_count: payload?.total_count ?? 0, - data: (payload?.data ?? []).map( - (row: EnterpriseConnectionTestRunJSON) => new EnterpriseConnectionTestRun(row, enterpriseConnectionId), - ), + data: (payload?.data ?? []).map((row: EnterpriseConnectionTestRunJSON) => new EnterpriseConnectionTestRun(row)), }; }; From 1ae26e3b1f8c6d2600a8350f22794b8c2945b4a0 Mon Sep 17 00:00:00 2001 From: Laura Beatris Date: Thu, 30 Apr 2026 15:31:30 -0700 Subject: [PATCH 8/8] Bump bundle limit --- packages/clerk-js/bundlewatch.config.json | 4 ++-- packages/clerk-js/src/core/resources/User.ts | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/clerk-js/bundlewatch.config.json b/packages/clerk-js/bundlewatch.config.json index 5b44126e81d..3aef7fb1570 100644 --- a/packages/clerk-js/bundlewatch.config.json +++ b/packages/clerk-js/bundlewatch.config.json @@ -1,10 +1,10 @@ { "files": [ { "path": "./dist/clerk.js", "maxSize": "543KB" }, - { "path": "./dist/clerk.browser.js", "maxSize": "68KB" }, + { "path": "./dist/clerk.browser.js", "maxSize": "70KB" }, { "path": "./dist/clerk.legacy.browser.js", "maxSize": "110KB" }, { "path": "./dist/clerk.no-rhc.js", "maxSize": "309KB" }, - { "path": "./dist/clerk.native.js", "maxSize": "68KB" }, + { "path": "./dist/clerk.native.js", "maxSize": "70KB" }, { "path": "./dist/vendors*.js", "maxSize": "7KB" }, { "path": "./dist/coinbase*.js", "maxSize": "36KB" }, { "path": "./dist/base-account-sdk*.js", "maxSize": "203KB" }, diff --git a/packages/clerk-js/src/core/resources/User.ts b/packages/clerk-js/src/core/resources/User.ts index 81866cc5ede..af80f6704bb 100644 --- a/packages/clerk-js/src/core/resources/User.ts +++ b/packages/clerk-js/src/core/resources/User.ts @@ -1,10 +1,11 @@ import { getFullName } from '@clerk/shared/internal/clerk-js/user'; -import { deepCamelToSnake } from '@clerk/shared/underscore'; import type { BackupCodeJSON, BackupCodeResource, + ClerkPaginatedResponse, CreateEmailAddressParams, CreateExternalAccountParams, + CreateMeEnterpriseConnectionParams, CreatePhoneNumberParams, CreateWeb3WalletParams, DeletedObjectJSON, @@ -13,18 +14,15 @@ import type { EnterpriseAccountResource, EnterpriseConnectionJSON, EnterpriseConnectionResource, - ExternalAccountJSON, - ExternalAccountResource, - ClerkPaginatedResponse, - CreateMeEnterpriseConnectionParams, EnterpriseConnectionTestRunInitJSON, - EnterpriseConnectionTestRunJSON, - EnterpriseConnectionTestRunsPaginatedJSON, EnterpriseConnectionTestRunInitResource, + EnterpriseConnectionTestRunJSON, EnterpriseConnectionTestRunResource, - GetEnterpriseConnectionTestRunsParams, + EnterpriseConnectionTestRunsPaginatedJSON, + ExternalAccountJSON, + ExternalAccountResource, GetEnterpriseConnectionsParams, - UpdateMeEnterpriseConnectionParams, + GetEnterpriseConnectionTestRunsParams, GetOrganizationMemberships, GetUserOrganizationInvitationsParams, GetUserOrganizationSuggestionsParams, @@ -36,6 +34,7 @@ import type { SetProfileImageParams, TOTPJSON, TOTPResource, + UpdateMeEnterpriseConnectionParams, UpdateUserParams, UpdateUserPasswordParams, UserJSON, @@ -44,9 +43,10 @@ import type { VerifyTOTPParams, Web3WalletResource, } from '@clerk/shared/types'; +import { deepCamelToSnake } from '@clerk/shared/underscore'; -import { unixEpochToDate } from '../../utils/date'; import { convertPageToOffsetSearchParams } from '../../utils/convertPageToOffsetSearchParams'; +import { unixEpochToDate } from '../../utils/date'; import { normalizeUnsafeMetadata } from '../../utils/resourceParams'; import { eventBus, events } from '../events'; import { addPaymentMethod, getPaymentMethods, initializePaymentMethod } from '../modules/billing';