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
| Arquivo | Responsabilidade |
|---|---|
services/organization/organization.service.ts | Busca as organizações disponíveis, resolve qual delas usar e troca por um token de contexto |
storage/organization/organization.storage.ts | Persiste a lista de organizações disponíveis e qual está selecionada |
services/auth/auth.storage.ts | Guarda 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.