diff --git a/.changeset/dry-students-reflect.md b/.changeset/dry-students-reflect.md new file mode 100644 index 0000000000..bc50e1b1a0 --- /dev/null +++ b/.changeset/dry-students-reflect.md @@ -0,0 +1,5 @@ +--- +'@clerk/clerk-js': patch +--- + +Emit session when permissions or role of the active memberships change. diff --git a/packages/clerk-js/src/core/resources/Organization.ts b/packages/clerk-js/src/core/resources/Organization.ts index ddb9b8f1ce..9fd7c6873a 100644 --- a/packages/clerk-js/src/core/resources/Organization.ts +++ b/packages/clerk-js/src/core/resources/Organization.ts @@ -8,6 +8,7 @@ import type { GetInvitationsParams, GetMembershipRequestParams, GetMemberships, + GetMembershipsParams, GetPendingInvitationsParams, GetRolesParams, InviteMemberParams, @@ -26,7 +27,6 @@ import type { UpdateMembershipParams, UpdateOrganizationParams, } from '@clerk/types'; -import type { GetMembershipsParams } from '@clerk/types'; import { unixEpochToDate } from '../../utils/date'; import { convertPageToOffset } from '../../utils/pagesToOffset'; @@ -109,11 +109,16 @@ export class Organization extends BaseResource implements OrganizationResource { }; getRoles = async (getRolesParams?: GetRolesParams) => { - return await BaseResource._fetch({ - path: `/organizations/${this.id}/roles`, - method: 'GET', - search: convertPageToOffset(getRolesParams) as any, - }).then(res => { + return await BaseResource._fetch( + { + path: `/organizations/${this.id}/roles`, + method: 'GET', + search: convertPageToOffset(getRolesParams) as any, + }, + { + forceUpdateClient: true, + }, + ).then(res => { const { data: roles, total_count } = res?.response as unknown as ClerkPaginatedResponse; return { @@ -126,11 +131,16 @@ export class Organization extends BaseResource implements OrganizationResource { getDomains = async ( getDomainParams?: GetDomainsParams, ): Promise> => { - return await BaseResource._fetch({ - path: `/organizations/${this.id}/domains`, - method: 'GET', - search: convertPageToOffset(getDomainParams) as any, - }) + return await BaseResource._fetch( + { + path: `/organizations/${this.id}/domains`, + method: 'GET', + search: convertPageToOffset(getDomainParams) as any, + }, + { + forceUpdateClient: true, + }, + ) .then(res => { const { data: invites, total_count } = res?.response as unknown as ClerkPaginatedResponse; @@ -197,13 +207,18 @@ export class Organization extends BaseResource implements OrganizationResource { deprecated('offset', 'Use `initialPage` instead in Organization.limit.', 'organization:getMemberships:offset'); } - return await BaseResource._fetch({ - path: `/organizations/${this.id}/memberships`, - method: 'GET', - search: isDeprecatedParams - ? getMembershipsParams - : (convertPageToOffset(getMembershipsParams as unknown as any) as any), - }) + return await BaseResource._fetch( + { + path: `/organizations/${this.id}/memberships`, + method: 'GET', + search: isDeprecatedParams + ? getMembershipsParams + : (convertPageToOffset(getMembershipsParams as unknown as any) as any), + }, + { + forceUpdateClient: true, + }, + ) .then(res => { if (isDeprecatedParams) { const organizationMembershipsJSON = res?.response as unknown as OrganizationMembershipJSON[]; @@ -248,11 +263,16 @@ export class Organization extends BaseResource implements OrganizationResource { getInvitations = async ( getInvitationsParams?: GetInvitationsParams, ): Promise> => { - return await BaseResource._fetch({ - path: `/organizations/${this.id}/invitations`, - method: 'GET', - search: convertPageToOffset(getInvitationsParams) as any, - }) + return await BaseResource._fetch( + { + path: `/organizations/${this.id}/invitations`, + method: 'GET', + search: convertPageToOffset(getInvitationsParams) as any, + }, + { + forceUpdateClient: true, + }, + ) .then(res => { const { data: requests, total_count } = res?.response as unknown as ClerkPaginatedResponse; diff --git a/packages/clerk-js/src/utils/memoizeStateListenerCallback.ts b/packages/clerk-js/src/utils/memoizeStateListenerCallback.ts index 9f061e38db..6c4d85dff2 100644 --- a/packages/clerk-js/src/utils/memoizeStateListenerCallback.ts +++ b/packages/clerk-js/src/utils/memoizeStateListenerCallback.ts @@ -28,7 +28,11 @@ function clientChanged(prev: ClientResource, next: ClientResource): boolean { } function sessionChanged(prev: SessionResource, next: SessionResource): boolean { - return prev.id !== next.id || prev.updatedAt.getTime() < next.updatedAt.getTime(); + return ( + prev.id !== next.id || + prev.updatedAt.getTime() < next.updatedAt.getTime() || + sessionUserMembershipPermissionsChanged(next, prev) + ); } function userChanged(prev: UserResource, next: UserResource): boolean { @@ -45,6 +49,25 @@ function userMembershipsChanged(prev: UserResource, next: UserResource): boolean ); } +function sessionUserMembershipPermissionsChanged(prev: SessionResource, next: SessionResource): boolean { + if (prev.lastActiveOrganizationId !== next.lastActiveOrganizationId) { + return true; + } + + const prevActiveMembership = prev.user?.organizationMemberships?.find( + mem => mem.organization.id === prev.lastActiveOrganizationId, + ); + + const nextActiveMembership = next.user?.organizationMemberships?.find( + mem => mem.organization.id === prev.lastActiveOrganizationId, + ); + + return ( + prevActiveMembership?.role !== nextActiveMembership?.role || + prevActiveMembership?.permissions?.length !== nextActiveMembership?.permissions?.length + ); +} + // TODO: Decide if this belongs in the resources function resourceChanged(prev: T, next: T): boolean { // support the setSession undefined intermediate state