Pular para o conteúdo principal

Contexto de Organização

Última atualização: 14 de julho de 2026

Visão Geral​

Estar autenticado no Keycloak não é suficiente para operar o app. O mesmo usuário pode ter acesso a mais de uma organização dentro do ecossistema Vidya, e cada chamada autenticada precisa saber em qual organização está operando. Essa resolução acontece depois do login, como parte da rotina de sincronização inicial, e resulta num segundo token, guardado e renovado separadamente do token principal descrito nos capítulos anteriores.

Arquivos-chave​

ArquivoResponsabilidade
services/organization/organization.service.tsBusca as organizações disponíveis, resolve qual delas usar e troca por um token de contexto
storage/organization/organization.storage.tsPersiste a lista de organizações disponíveis e qual está selecionada
services/auth/auth.storage.tsGuarda o token de contexto resultante, no mesmo lugar onde o token principal é guardado
features/organization/*Modal de seleção de organização, usado quando existe mais de uma opção

Como o contexto é resolvido​

OrganizationService.getOrganization() é chamado como parte da rotina de sincronização que roda depois de um login, não pela tela de login diretamente. Ele busca as organizações que o usuário tem acesso, decide qual delas usar, e troca essa escolha por um token de contexto:

// organization.service.ts
async getOrganization(organizationId?: string): Promise<AuthContextsToken> {
const contexts = await this.fetchAuthContexts();
setOrganizationContextsStorage(contexts);

let selectedOrganization = organizationId
? contexts?.find?.((item) => item.organizationId === organizationId)
: undefined;

if (!selectedOrganization && contexts?.length > 1) {
const { organizationSelector } = await import("@/features/organization");
const selected = await organizationSelector(contexts);
if (selected) {
selectedOrganization = selected;
}
}

if (!selectedOrganization) {
selectedOrganization = contexts?.[0];
}

if (selectedOrganization) {
setSelectedOrganizationStorage(selectedOrganization);
}

const resolvedOrganizationId = selectedOrganization?.organizationId;
if (!resolvedOrganizationId) {
throw new Error("Organization context not found");
}

const authContexts = await this.exchangeAuthContextToken(resolvedOrganizationId);

if (authContexts.token) {
AuthStorage.saveAuthContextsTokens(authContexts);
}

return authContexts;
},

A ordem de decisão é direta. Se um organizationId específico já foi passado e existe entre os contextos disponíveis, ele é usado sem perguntar nada ao usuário. Se não houver um id específico e existir mais de uma organização disponível, o app abre o seletor de organização e espera a escolha. Se depois disso ainda não houver nenhuma selecionada, por exemplo quando existe só uma organização disponível, o app simplesmente usa a primeira da lista.

O que fica persistido​

Duas coisas diferentes ficam guardadas para o contexto de organização, e elas moram em lugares diferentes de propósito. A lista de organizações disponíveis e qual delas está selecionada ficam em storage/organization/organization.storage.ts, porque são dados de preferência e de catálogo. O token de contexto propriamente dito, que é o que efetivamente autoriza as chamadas, fica junto dos tokens de autenticação em AuthStorage, como um AuthContextsToken:

// auth.storage.ts
export interface AuthContextsToken {
token: string;
expiresIn: number;
userId: string;
organizationId: string;
erp: string;
urlErp: string;
orgToken: string;
database: string;
}

Esse token é o que a camada de API usa no cabeçalho X-Contextual-Token, descrito em Visão Geral, e também carrega informações auxiliares sobre o ERP e o banco de dados daquela organização, que outras partes do app usam para saber como se comportar dentro daquele contexto específico.

Nunca sobrescrever o contexto silenciosamente​

Essa é a regra mais importante deste capítulo, e ela existe porque trocar de organização sem os cuidados certos deixaria o app operando com dados de uma organização mas com um token de outra, ou vice-versa. Sempre que o contexto de organização precisar ser renovado, e não escolhido do zero, o caminho correto é refreshSelectedOrganizationContext():

// organization.service.ts
async refreshSelectedOrganizationContext(): Promise<AuthContextsToken> {
const contexts = await this.fetchAuthContexts();
setOrganizationContextsStorage(contexts);

const selectedOrganization = this.getSelectedOrganization();
const selectedOrganizationId = selectedOrganization?.organizationId?.trim();

if (!selectedOrganizationId) {
throw new Error("Selected organization not found");
}

const matchedOrganization = contexts.find(
(item) => item.organizationId === selectedOrganizationId,
);

if (!matchedOrganization) {
throw new Error("Selected organization is no longer available");
}

setSelectedOrganizationStorage(matchedOrganization);

const authContexts = await this.exchangeAuthContextToken(matchedOrganization.organizationId);

if (authContexts.token) {
AuthStorage.saveAuthContextsTokens(authContexts);
}

return authContexts;
},

Note que essa função confirma que a organização selecionada ainda existe na lista atualizada de contextos antes de renovar o token. Se a organização selecionada tiver deixado de existir, por exemplo por ter sido removida do usuário no meio de uma sessão, a função lança um erro em vez de silenciosamente cair para outra organização qualquer.

Essa é exatamente a função chamada por AuthService.refreshTokens() depois de renovar o token principal, como descrito em Refresh de Token. É assim que os dois tokens, o principal e o de contexto, se mantêm renovados juntos sem que um novo caminho de código precise coordenar essa ordem manualmente.

Armadilhas conhecidas​

Nunca chame exchangeAuthContextToken diretamente para trocar de organização fora de getOrganization ou refreshSelectedOrganizationContext. Fazer isso puxaria um novo token de contexto sem atualizar a lista de organizações disponíveis nem a organização selecionada persistida, deixando esses dados fora de sincronia entre si. Se o usuário trocar de organização explicitamente pela UI, essa troca deve persistir a nova seleção e trocar o token contextual de forma explícita, nunca como um efeito colateral de outra operação.