@greatapps/common 1.1.796 → 1.1.798

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@ import greatCache from "@greatapps/cache";
2
2
  import { ApiError } from "../../../infra/api/types";
3
3
  import { normalizeHostname } from "../utils/normalize-hostname";
4
4
  import {
5
- DEFAULT_WHITELABEL_DOMAIN,
5
+ DEFAULT_WHITELABEL_ID,
6
6
  MISSING_WHITELABEL_CACHE_TTL_SECONDS,
7
7
  WHITELABEL_CACHE_TTL_SECONDS
8
8
  } from "../constants/whitelabel.constants";
@@ -43,6 +43,9 @@ class WhitelabelService {
43
43
  getMissingCacheKey(hostname) {
44
44
  return `whitelabel-missing-${hostname}`;
45
45
  }
46
+ getDefaultCacheKey() {
47
+ return `whitelabel-token-id-${DEFAULT_WHITELABEL_ID}`;
48
+ }
46
49
  async fetchFromApi(hostname) {
47
50
  const apiUrl = this.getApiUrl();
48
51
  const whitelabelMasterToken = this.getToken();
@@ -78,11 +81,6 @@ class WhitelabelService {
78
81
  }
79
82
  return result.data[0];
80
83
  }
81
- /**
82
- * Cache (positivo) + fetch + validação de hostname pra um domínio já normalizado. Usado tanto
83
- * pelo host da requisição quanto pelo domínio padrão — sem fallback embutido, pra `getDefault`
84
- * não poder recursar nela mesma.
85
- */
86
84
  async lookup(hostname) {
87
85
  const cache = this.createCache();
88
86
  const cacheKey = this.getCacheKey(hostname);
@@ -118,23 +116,40 @@ class WhitelabelService {
118
116
  await cache.insert(cacheKey, JSON.stringify(data), WHITELABEL_CACHE_TTL_SECONDS);
119
117
  return data;
120
118
  }
121
- /**
122
- * Whitelabel padrão (Great), destino de todo host sem whitelabel própria. Resolve pelo mesmo
123
- * `lookup`, então herda o cache positivo — mas nunca cai em `getTokenByDomain`, que teria
124
- * fallback: aqui a ausência é falha de plataforma, não caso esperado.
125
- */
126
119
  async getDefault() {
127
- const domain = normalizeHostname(
128
- process.env.WHITELABEL_DEFAULT_DOMAIN || DEFAULT_WHITELABEL_DOMAIN
129
- );
130
- const data = await this.lookup(domain);
131
- if (!data) {
120
+ const cache = this.createCache();
121
+ const cacheKey = this.getDefaultCacheKey();
122
+ const cachedData = await cache.select(cacheKey);
123
+ if (cachedData.status == 1 && "data" in cachedData && cachedData.data) {
124
+ const cachedWhitelabel = JSON.parse(cachedData.data);
125
+ return cachedWhitelabel;
126
+ }
127
+ const apiUrl = this.getApiUrl();
128
+ const masterToken = this.getToken();
129
+ const url = `${apiUrl}/v1/pt-br/${DEFAULT_WHITELABEL_ID}/whitelabels/${DEFAULT_WHITELABEL_ID}`;
130
+ const response = await fetch(url, {
131
+ method: "GET",
132
+ headers: {
133
+ authorization: masterToken
134
+ }
135
+ });
136
+ if (!response.ok) {
132
137
  throw new ApiError(
133
- `Default whitelabel not found: ${domain}`,
138
+ `Default whitelabel not found: ${DEFAULT_WHITELABEL_ID}`,
134
139
  "WL_DEFAULT_UNAVAILABLE",
135
140
  500
136
141
  );
137
142
  }
143
+ const result = await response.json();
144
+ if (result.status !== 1 || !result.data?.length) {
145
+ throw new ApiError(
146
+ `Default whitelabel not found: ${DEFAULT_WHITELABEL_ID}`,
147
+ "WL_DEFAULT_UNAVAILABLE",
148
+ 500
149
+ );
150
+ }
151
+ const data = { ...result.data[0], token: masterToken };
152
+ await cache.insert(cacheKey, JSON.stringify(data), WHITELABEL_CACHE_TTL_SECONDS);
138
153
  return data;
139
154
  }
140
155
  async getTokenByDomain(hostname) {
@@ -216,6 +231,7 @@ class WhitelabelService {
216
231
  });
217
232
  await cache.delete(cacheKey);
218
233
  await cache.delete(missingCacheKey);
234
+ await cache.delete(this.getDefaultCacheKey());
219
235
  const data = await this.getTokenByDomain(hostname);
220
236
  console.log("[WhitelabelService] Cache revalidated for domain", {
221
237
  hostname
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/modules/whitelabel/services/whitelabel.service.ts"],"sourcesContent":["import greatCache from \"@greatapps/cache\";\nimport { ApiError } from \"../../../infra/api/types\";\nimport { WhitelabelTokenApiResponse, WhitelabelTokenData } from \"../schema\";\nimport { normalizeHostname } from \"../utils/normalize-hostname\";\nimport {\n DEFAULT_WHITELABEL_DOMAIN,\n MISSING_WHITELABEL_CACHE_TTL_SECONDS,\n WHITELABEL_CACHE_TTL_SECONDS,\n} from \"../constants/whitelabel.constants\";\n\nclass WhitelabelService {\n private getApiUrl(): string {\n const apiUrl = process.env.GAPPS_R3_API_URL;\n if (!apiUrl) {\n throw new ApiError(\n \"GAPPS_R3_API_URL not configured\",\n \"CONFIG_ERROR\",\n 500,\n );\n }\n return apiUrl;\n }\n\n private getToken(): string {\n const token = process.env.WHITELABEL_TOKEN_MASTER;\n if (!token) {\n throw new ApiError(\n \"WHITELABEL_TOKEN_MASTER not configured\",\n \"CONFIG_ERROR\",\n 500,\n );\n }\n return token;\n }\n\n private createCache() {\n return new greatCache({\n service: \"whitelabel-service\",\n version: \"1.0.3\",\n domain: \"whitelabel-cache.greatapps.com.br\",\n ambient: process.env.NODE_ENV || \"development\",\n });\n }\n\n private getCacheKey(hostname: string): string {\n return `whitelabel-token-${hostname}`;\n }\n\n private getMissingCacheKey(hostname: string): string {\n return `whitelabel-missing-${hostname}`;\n }\n\n private async fetchFromApi(hostname: string): Promise<WhitelabelTokenData | null> {\n const apiUrl = this.getApiUrl();\n const whitelabelMasterToken = this.getToken();\n const url = `${apiUrl}/v1/pt-br/1/whitelabel/${hostname}/token`;\n\n console.log(\"[WhitelabelService] Fetching token for domain\", { url });\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n authorization: whitelabelMasterToken,\n },\n });\n\n // Host sem whitelabel ativa é caso esperado (domínio novo, subdomínio livre) — não é falha de\n // rede, então nem lê o corpo: cai no fallback do whitelabel padrão. 403 NÃO entra aqui: a rota\n // emissora nunca responde 403, quem responde é borda (WAF, Access, rate limit) — tratar como\n // ausência de whitelabel faria um incidente de borda derrubar toda whitelabel de cliente no\n // whitelabel padrão, servindo branding e token de API errados em silêncio.\n if (response.status === 404) {\n console.log(\"[WhitelabelService] No whitelabel for domain\", {\n hostname,\n status: response.status,\n });\n return null;\n }\n\n if (!response.ok) {\n console.error(\"[WhitelabelService] Failed to fetch whitelabel token\", {\n hostname,\n status: response.status,\n });\n throw new ApiError(\n `Failed to fetch whitelabel token: ${response.status}`,\n \"FETCH_ERROR\",\n response.status,\n );\n }\n\n const result: WhitelabelTokenApiResponse = await response.json();\n\n if (result.status !== 1 || !result.data?.length) {\n return null;\n }\n\n return result.data[0];\n }\n\n /**\n * Cache (positivo) + fetch + validação de hostname pra um domínio já normalizado. Usado tanto\n * pelo host da requisição quanto pelo domínio padrão — sem fallback embutido, pra `getDefault`\n * não poder recursar nela mesma.\n */\n private async lookup(hostname: string): Promise<WhitelabelTokenData | null> {\n const cache = this.createCache();\n const cacheKey = this.getCacheKey(hostname);\n\n const cachedData = await cache.select(cacheKey);\n console.debug(\"[WhitelabelService] Cache lookup for domain\", {\n cacheKey,\n cachedData,\n });\n\n if (cachedData.status == 1 && \"data\" in cachedData && cachedData.data) {\n const cachedWhitelabel: WhitelabelTokenData = JSON.parse(cachedData.data);\n\n if (this.#matchesHostname(cachedWhitelabel, hostname)) {\n console.log(\"[WhitelabelService] Cache hit for domain\", { hostname });\n return cachedWhitelabel;\n }\n\n console.error(\"[WhitelabelService] Cached whitelabel does not match hostname\", {\n hostname,\n cacheKey,\n whitelabelId: cachedWhitelabel.id,\n whitelabelDomain: cachedWhitelabel.domain,\n });\n\n await cache.delete(cacheKey);\n }\n\n const data = await this.fetchFromApi(hostname);\n if (!data) return null;\n\n if (!this.#matchesHostname(data, hostname)) {\n console.error(\"[WhitelabelService] API returned whitelabel of another hostname\", {\n hostname,\n whitelabelId: data.id,\n whitelabelDomain: data.domain,\n });\n return data;\n }\n\n await cache.insert(cacheKey, JSON.stringify(data), WHITELABEL_CACHE_TTL_SECONDS);\n\n return data;\n }\n\n /**\n * Whitelabel padrão (Great), destino de todo host sem whitelabel própria. Resolve pelo mesmo\n * `lookup`, então herda o cache positivo — mas nunca cai em `getTokenByDomain`, que teria\n * fallback: aqui a ausência é falha de plataforma, não caso esperado.\n */\n private async getDefault(): Promise<WhitelabelTokenData> {\n const domain = normalizeHostname(\n process.env.WHITELABEL_DEFAULT_DOMAIN || DEFAULT_WHITELABEL_DOMAIN,\n );\n\n const data = await this.lookup(domain);\n\n if (!data) {\n throw new ApiError(\n `Default whitelabel not found: ${domain}`,\n \"WL_DEFAULT_UNAVAILABLE\",\n 500,\n );\n }\n\n return data;\n }\n\n async getTokenByDomain(hostname: string): Promise<WhitelabelTokenData> {\n hostname = `${normalizeHostname(process.env.WHITELABEL_DOMAIN || hostname)}`;\n console.debug(\"[WhitelabelService] Getting token for domain\", {\n hostname,\n byEnv: process.env.WHITELABEL_DOMAIN,\n });\n\n const cache = this.createCache();\n const missingCacheKey = this.getMissingCacheKey(hostname);\n\n const cachedMissing = await cache.select(missingCacheKey);\n if (cachedMissing.status == 1) {\n console.log(\"[WhitelabelService] Cached miss for domain, using default\", { hostname });\n return this.getDefault();\n }\n\n const data = await this.lookup(hostname);\n if (data) return data;\n\n console.log(\"[WhitelabelService] No whitelabel for domain, falling back to default\", {\n hostname,\n });\n await cache.insert(missingCacheKey, \"1\", MISSING_WHITELABEL_CACHE_TTL_SECONDS);\n\n return this.getDefault();\n }\n\n #matchesHostname(data: WhitelabelTokenData, hostname: string): boolean {\n if (!data.domain) return true;\n\n try {\n return normalizeHostname(new URL(data.domain).hostname) === hostname;\n } catch {\n return true;\n }\n }\n\n async getTokenByWhitelabelId(idWl: number): Promise<string> {\n const apiUrl = this.getApiUrl();\n const masterToken = this.getToken();\n const url = `${apiUrl}/v1/pt-br/${idWl}/tokens?limit=1&page=1&sort=id:desc`;\n console.debug(\"[WhitelabelService] Fetching token by whitelabel ID\", {\n url,\n config: {\n method: \"GET\",\n headers: { authorization: masterToken },\n },\n });\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: { authorization: masterToken },\n });\n\n console.debug(\"[WhitelabelService] Response received for whitelabel ID\", {\n response,\n });\n\n if (!response.ok) {\n throw new ApiError(\n `Failed to fetch whitelabel token: ${response.status}`,\n \"WL_TOKEN_NOT_FOUND\",\n response.status,\n );\n }\n\n const result: WhitelabelTokenApiResponse = await response.json();\n\n if (result.status !== 1 || !result.data?.length) {\n throw new ApiError(\n \"Token do whitelabel não encontrado\",\n \"WL_TOKEN_NOT_FOUND\",\n 404,\n );\n }\n\n return result.data[0].token;\n }\n\n async revalidateByDomain(hostname: string): Promise<WhitelabelTokenData> {\n hostname = `${normalizeHostname(process.env.WHITELABEL_DOMAIN || hostname)}`;\n console.debug(\"[WhitelabelService] Getting token for domain\", {\n hostname,\n byEnv: process.env.WHITELABEL_DOMAIN,\n });\n\n const cache = this.createCache();\n const cacheKey = this.getCacheKey(hostname);\n const missingCacheKey = this.getMissingCacheKey(hostname);\n\n console.log(\"[WhitelabelService] Revalidating cache for domain\", {\n hostname,\n cacheKey,\n });\n\n await cache.delete(cacheKey);\n await cache.delete(missingCacheKey);\n\n const data = await this.getTokenByDomain(hostname);\n\n console.log(\"[WhitelabelService] Cache revalidated for domain\", {\n hostname,\n });\n\n return data;\n }\n}\n\nexport const whitelabelService = new WhitelabelService();\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../src/modules/whitelabel/services/whitelabel.service.ts"],"sourcesContent":["import greatCache from \"@greatapps/cache\";\nimport { ApiError } from \"../../../infra/api/types\";\nimport { WhitelabelApiResponse, WhitelabelTokenApiResponse, WhitelabelTokenData } from \"../schema\";\nimport { normalizeHostname } from \"../utils/normalize-hostname\";\nimport {\n DEFAULT_WHITELABEL_ID,\n MISSING_WHITELABEL_CACHE_TTL_SECONDS,\n WHITELABEL_CACHE_TTL_SECONDS,\n} from \"../constants/whitelabel.constants\";\n\nclass WhitelabelService {\n private getApiUrl(): string {\n const apiUrl = process.env.GAPPS_R3_API_URL;\n if (!apiUrl) {\n throw new ApiError(\n \"GAPPS_R3_API_URL not configured\",\n \"CONFIG_ERROR\",\n 500,\n );\n }\n return apiUrl;\n }\n\n private getToken(): string {\n const token = process.env.WHITELABEL_TOKEN_MASTER;\n if (!token) {\n throw new ApiError(\n \"WHITELABEL_TOKEN_MASTER not configured\",\n \"CONFIG_ERROR\",\n 500,\n );\n }\n return token;\n }\n\n private createCache() {\n return new greatCache({\n service: \"whitelabel-service\",\n version: \"1.0.3\",\n domain: \"whitelabel-cache.greatapps.com.br\",\n ambient: process.env.NODE_ENV || \"development\",\n });\n }\n\n private getCacheKey(hostname: string): string {\n return `whitelabel-token-${hostname}`;\n }\n\n private getMissingCacheKey(hostname: string): string {\n return `whitelabel-missing-${hostname}`;\n }\n\n private getDefaultCacheKey(): string {\n return `whitelabel-token-id-${DEFAULT_WHITELABEL_ID}`;\n }\n\n private async fetchFromApi(hostname: string): Promise<WhitelabelTokenData | null> {\n const apiUrl = this.getApiUrl();\n const whitelabelMasterToken = this.getToken();\n const url = `${apiUrl}/v1/pt-br/1/whitelabel/${hostname}/token`;\n\n console.log(\"[WhitelabelService] Fetching token for domain\", { url });\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n authorization: whitelabelMasterToken,\n },\n });\n\n if (response.status === 404) {\n console.log(\"[WhitelabelService] No whitelabel for domain\", {\n hostname,\n status: response.status,\n });\n return null;\n }\n\n if (!response.ok) {\n console.error(\"[WhitelabelService] Failed to fetch whitelabel token\", {\n hostname,\n status: response.status,\n });\n throw new ApiError(\n `Failed to fetch whitelabel token: ${response.status}`,\n \"FETCH_ERROR\",\n response.status,\n );\n }\n\n const result: WhitelabelTokenApiResponse = await response.json();\n\n if (result.status !== 1 || !result.data?.length) {\n return null;\n }\n\n return result.data[0];\n }\n\n private async lookup(hostname: string): Promise<WhitelabelTokenData | null> {\n const cache = this.createCache();\n const cacheKey = this.getCacheKey(hostname);\n\n const cachedData = await cache.select(cacheKey);\n console.debug(\"[WhitelabelService] Cache lookup for domain\", {\n cacheKey,\n cachedData,\n });\n\n if (cachedData.status == 1 && \"data\" in cachedData && cachedData.data) {\n const cachedWhitelabel: WhitelabelTokenData = JSON.parse(cachedData.data);\n\n if (this.#matchesHostname(cachedWhitelabel, hostname)) {\n console.log(\"[WhitelabelService] Cache hit for domain\", { hostname });\n return cachedWhitelabel;\n }\n\n console.error(\"[WhitelabelService] Cached whitelabel does not match hostname\", {\n hostname,\n cacheKey,\n whitelabelId: cachedWhitelabel.id,\n whitelabelDomain: cachedWhitelabel.domain,\n });\n\n await cache.delete(cacheKey);\n }\n\n const data = await this.fetchFromApi(hostname);\n if (!data) return null;\n\n if (!this.#matchesHostname(data, hostname)) {\n console.error(\"[WhitelabelService] API returned whitelabel of another hostname\", {\n hostname,\n whitelabelId: data.id,\n whitelabelDomain: data.domain,\n });\n return data;\n }\n\n await cache.insert(cacheKey, JSON.stringify(data), WHITELABEL_CACHE_TTL_SECONDS);\n\n return data;\n }\n\n private async getDefault(): Promise<WhitelabelTokenData> {\n const cache = this.createCache();\n const cacheKey = this.getDefaultCacheKey();\n\n const cachedData = await cache.select(cacheKey);\n if (cachedData.status == 1 && \"data\" in cachedData && cachedData.data) {\n const cachedWhitelabel: WhitelabelTokenData = JSON.parse(cachedData.data);\n return cachedWhitelabel;\n }\n\n const apiUrl = this.getApiUrl();\n const masterToken = this.getToken();\n const url = `${apiUrl}/v1/pt-br/${DEFAULT_WHITELABEL_ID}/whitelabels/${DEFAULT_WHITELABEL_ID}`;\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n authorization: masterToken,\n },\n });\n\n if (!response.ok) {\n throw new ApiError(\n `Default whitelabel not found: ${DEFAULT_WHITELABEL_ID}`,\n \"WL_DEFAULT_UNAVAILABLE\",\n 500,\n );\n }\n\n const result: WhitelabelApiResponse = await response.json();\n\n if (result.status !== 1 || !result.data?.length) {\n throw new ApiError(\n `Default whitelabel not found: ${DEFAULT_WHITELABEL_ID}`,\n \"WL_DEFAULT_UNAVAILABLE\",\n 500,\n );\n }\n\n const data: WhitelabelTokenData = { ...result.data[0], token: masterToken };\n\n await cache.insert(cacheKey, JSON.stringify(data), WHITELABEL_CACHE_TTL_SECONDS);\n\n return data;\n }\n\n async getTokenByDomain(hostname: string): Promise<WhitelabelTokenData> {\n hostname = `${normalizeHostname(process.env.WHITELABEL_DOMAIN || hostname)}`;\n console.debug(\"[WhitelabelService] Getting token for domain\", {\n hostname,\n byEnv: process.env.WHITELABEL_DOMAIN,\n });\n\n const cache = this.createCache();\n const missingCacheKey = this.getMissingCacheKey(hostname);\n\n const cachedMissing = await cache.select(missingCacheKey);\n if (cachedMissing.status == 1) {\n console.log(\"[WhitelabelService] Cached miss for domain, using default\", { hostname });\n return this.getDefault();\n }\n\n const data = await this.lookup(hostname);\n if (data) return data;\n\n console.log(\"[WhitelabelService] No whitelabel for domain, falling back to default\", {\n hostname,\n });\n await cache.insert(missingCacheKey, \"1\", MISSING_WHITELABEL_CACHE_TTL_SECONDS);\n\n return this.getDefault();\n }\n\n #matchesHostname(data: WhitelabelTokenData, hostname: string): boolean {\n if (!data.domain) return true;\n\n try {\n return normalizeHostname(new URL(data.domain).hostname) === hostname;\n } catch {\n return true;\n }\n }\n\n async getTokenByWhitelabelId(idWl: number): Promise<string> {\n const apiUrl = this.getApiUrl();\n const masterToken = this.getToken();\n const url = `${apiUrl}/v1/pt-br/${idWl}/tokens?limit=1&page=1&sort=id:desc`;\n console.debug(\"[WhitelabelService] Fetching token by whitelabel ID\", {\n url,\n config: {\n method: \"GET\",\n headers: { authorization: masterToken },\n },\n });\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: { authorization: masterToken },\n });\n\n console.debug(\"[WhitelabelService] Response received for whitelabel ID\", {\n response,\n });\n\n if (!response.ok) {\n throw new ApiError(\n `Failed to fetch whitelabel token: ${response.status}`,\n \"WL_TOKEN_NOT_FOUND\",\n response.status,\n );\n }\n\n const result: WhitelabelTokenApiResponse = await response.json();\n\n if (result.status !== 1 || !result.data?.length) {\n throw new ApiError(\n \"Token do whitelabel não encontrado\",\n \"WL_TOKEN_NOT_FOUND\",\n 404,\n );\n }\n\n return result.data[0].token;\n }\n\n async revalidateByDomain(hostname: string): Promise<WhitelabelTokenData> {\n hostname = `${normalizeHostname(process.env.WHITELABEL_DOMAIN || hostname)}`;\n console.debug(\"[WhitelabelService] Getting token for domain\", {\n hostname,\n byEnv: process.env.WHITELABEL_DOMAIN,\n });\n\n const cache = this.createCache();\n const cacheKey = this.getCacheKey(hostname);\n const missingCacheKey = this.getMissingCacheKey(hostname);\n\n console.log(\"[WhitelabelService] Revalidating cache for domain\", {\n hostname,\n cacheKey,\n });\n\n await cache.delete(cacheKey);\n await cache.delete(missingCacheKey);\n await cache.delete(this.getDefaultCacheKey());\n\n const data = await this.getTokenByDomain(hostname);\n\n console.log(\"[WhitelabelService] Cache revalidated for domain\", {\n hostname,\n });\n\n return data;\n }\n}\n\nexport const whitelabelService = new WhitelabelService();\n"],"mappings":"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","names":[]}
@@ -11,6 +11,7 @@ function makeSessionHandlers(opts = {}) {
11
11
  const whitelabel = opts.whitelabel ?? createWhitelabelTokenMock();
12
12
  return [
13
13
  http.get(`${base}/v1/:locale/:wl/whitelabel/:host/token`, () => apiFind(whitelabel)),
14
+ http.get(`${base}/v1/:locale/:wl/whitelabels/:id`, () => apiFind(whitelabel)),
14
15
  http.get(`${base}/v1/:locale/:wl/tokens`, () => apiFind(whitelabel)),
15
16
  // The users path has more segments, so it must come before /accounts/:acc.
16
17
  http.get(`${base}/v1/:locale/:wl/accounts/:acc/users/:user`, () => apiFind(user)),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/testing/msw/session.handlers.ts"],"sourcesContent":["import { http } from 'msw';\nimport type { Account } from '../../modules/accounts/types';\nimport type { User } from '../../modules/users/schema';\nimport type { WhitelabelTokenData } from '../../modules/whitelabel/schema';\nimport { TEST_API } from '../constants';\nimport { createAccountMock } from '../factories/account.factory';\nimport { createUserMock } from '../factories/user.factory';\nimport { createWhitelabelTokenMock } from '../factories/whitelabel.factory';\nimport { apiFind } from './envelope';\n\nexport interface SessionHandlersOptions {\n gappsBase?: string;\n user?: User;\n account?: Account;\n whitelabel?: WhitelabelTokenData;\n}\n\n// Server-side session resolution any app triggers when booting an authenticated route with\n// DUMMY_AUTH_TOKEN (whitelabel by domain, token by wl id, user, account). Compose with app\n// handlers: setupServer(...makeSessionHandlers(), ...myPageHandlers).\nexport function makeSessionHandlers(opts: SessionHandlersOptions = {}) {\n const base = opts.gappsBase ?? TEST_API.gapps;\n const user = opts.user ?? createUserMock();\n const account = opts.account ?? createAccountMock();\n const whitelabel = opts.whitelabel ?? createWhitelabelTokenMock();\n\n return [\n http.get(`${base}/v1/:locale/:wl/whitelabel/:host/token`, () => apiFind(whitelabel)),\n http.get(`${base}/v1/:locale/:wl/tokens`, () => apiFind(whitelabel)),\n // The users path has more segments, so it must come before /accounts/:acc.\n http.get(`${base}/v1/:locale/:wl/accounts/:acc/users/:user`, () => apiFind(user)),\n http.get(`${base}/v1/:locale/:wl/accounts/:acc`, () => apiFind(account)),\n ];\n}\n"],"mappings":"AAAA,SAAS,YAAY;AAIrB,SAAS,gBAAgB;AACzB,SAAS,yBAAyB;AAClC,SAAS,sBAAsB;AAC/B,SAAS,iCAAiC;AAC1C,SAAS,eAAe;AAYjB,SAAS,oBAAoB,OAA+B,CAAC,GAAG;AACrE,QAAM,OAAO,KAAK,aAAa,SAAS;AACxC,QAAM,OAAO,KAAK,QAAQ,eAAe;AACzC,QAAM,UAAU,KAAK,WAAW,kBAAkB;AAClD,QAAM,aAAa,KAAK,cAAc,0BAA0B;AAEhE,SAAO;AAAA,IACL,KAAK,IAAI,GAAG,IAAI,0CAA0C,MAAM,QAAQ,UAAU,CAAC;AAAA,IACnF,KAAK,IAAI,GAAG,IAAI,0BAA0B,MAAM,QAAQ,UAAU,CAAC;AAAA;AAAA,IAEnE,KAAK,IAAI,GAAG,IAAI,6CAA6C,MAAM,QAAQ,IAAI,CAAC;AAAA,IAChF,KAAK,IAAI,GAAG,IAAI,iCAAiC,MAAM,QAAQ,OAAO,CAAC;AAAA,EACzE;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../src/testing/msw/session.handlers.ts"],"sourcesContent":["import { http } from 'msw';\nimport type { Account } from '../../modules/accounts/types';\nimport type { User } from '../../modules/users/schema';\nimport type { WhitelabelTokenData } from '../../modules/whitelabel/schema';\nimport { TEST_API } from '../constants';\nimport { createAccountMock } from '../factories/account.factory';\nimport { createUserMock } from '../factories/user.factory';\nimport { createWhitelabelTokenMock } from '../factories/whitelabel.factory';\nimport { apiFind } from './envelope';\n\nexport interface SessionHandlersOptions {\n gappsBase?: string;\n user?: User;\n account?: Account;\n whitelabel?: WhitelabelTokenData;\n}\n\n// Server-side session resolution any app triggers when booting an authenticated route with\n// DUMMY_AUTH_TOKEN (whitelabel by domain, token by wl id, user, account). Compose with app\n// handlers: setupServer(...makeSessionHandlers(), ...myPageHandlers).\nexport function makeSessionHandlers(opts: SessionHandlersOptions = {}) {\n const base = opts.gappsBase ?? TEST_API.gapps;\n const user = opts.user ?? createUserMock();\n const account = opts.account ?? createAccountMock();\n const whitelabel = opts.whitelabel ?? createWhitelabelTokenMock();\n\n return [\n http.get(`${base}/v1/:locale/:wl/whitelabel/:host/token`, () => apiFind(whitelabel)),\n http.get(`${base}/v1/:locale/:wl/whitelabels/:id`, () => apiFind(whitelabel)),\n http.get(`${base}/v1/:locale/:wl/tokens`, () => apiFind(whitelabel)),\n // The users path has more segments, so it must come before /accounts/:acc.\n http.get(`${base}/v1/:locale/:wl/accounts/:acc/users/:user`, () => apiFind(user)),\n http.get(`${base}/v1/:locale/:wl/accounts/:acc`, () => apiFind(account)),\n ];\n}\n"],"mappings":"AAAA,SAAS,YAAY;AAIrB,SAAS,gBAAgB;AACzB,SAAS,yBAAyB;AAClC,SAAS,sBAAsB;AAC/B,SAAS,iCAAiC;AAC1C,SAAS,eAAe;AAYjB,SAAS,oBAAoB,OAA+B,CAAC,GAAG;AACrE,QAAM,OAAO,KAAK,aAAa,SAAS;AACxC,QAAM,OAAO,KAAK,QAAQ,eAAe;AACzC,QAAM,UAAU,KAAK,WAAW,kBAAkB;AAClD,QAAM,aAAa,KAAK,cAAc,0BAA0B;AAEhE,SAAO;AAAA,IACL,KAAK,IAAI,GAAG,IAAI,0CAA0C,MAAM,QAAQ,UAAU,CAAC;AAAA,IACnF,KAAK,IAAI,GAAG,IAAI,mCAAmC,MAAM,QAAQ,UAAU,CAAC;AAAA,IAC5E,KAAK,IAAI,GAAG,IAAI,0BAA0B,MAAM,QAAQ,UAAU,CAAC;AAAA;AAAA,IAEnE,KAAK,IAAI,GAAG,IAAI,6CAA6C,MAAM,QAAQ,IAAI,CAAC;AAAA,IAChF,KAAK,IAAI,GAAG,IAAI,iCAAiC,MAAM,QAAQ,OAAO,CAAC;AAAA,EACzE;AACF;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@greatapps/common",
3
- "version": "1.1.796",
3
+ "version": "1.1.798",
4
4
  "description": "Shared library for GreatApps frontend applications",
5
5
  "main": "./dist/index.mjs",
6
6
  "types": "./src/index.ts",
@@ -20,7 +20,7 @@ import { useUpdateBillingData } from '../../../modules/accounts/hooks/useAccount
20
20
  import { COUNTRIES } from '../../../utils/countries';
21
21
  import { readGeoCountry } from '../../../utils/geo-country';
22
22
  import { BR_STATE_OPTIONS } from '../../../utils/constants/br-states';
23
- import { formatCNPJ, formatCPF, formatPostalCode } from '../../../utils/format/masks';
23
+ import { formatCPFCNPJ, formatPostalCode } from '../../../utils/format/masks';
24
24
  import { isValidCNPJ, isValidCPF } from '../../../utils/validators/common';
25
25
  import type { Account } from '../../../modules/accounts/types';
26
26
  import type { UpdateBillingDataRequest } from '../../../modules/accounts/types';
@@ -28,7 +28,6 @@ import type { UpdateBillingDataRequest } from '../../../modules/accounts/types';
28
28
  type TranslateFn = (key: any, values?: Record<string, string | number>) => string;
29
29
 
30
30
  type BillingDataFormValues = {
31
- personType: 'pf' | 'pj';
32
31
  document: string;
33
32
  financialName: string;
34
33
  financialEmail: string;
@@ -55,9 +54,12 @@ const BR_REQUIRED_ADDRESS_FIELDS: readonly (keyof BillingDataFormValues)[] = [
55
54
  'state',
56
55
  ];
57
56
 
57
+ function isCompanyDocument(document: string): boolean {
58
+ return document.replace(/\D/g, '').length > 11;
59
+ }
60
+
58
61
  function buildBillingDataSchema(translate: TranslateFn) {
59
62
  const base = z.object({
60
- personType: z.enum(['pf', 'pj']),
61
63
  document: z.string().trim(),
62
64
  financialName: z
63
65
  .string()
@@ -99,7 +101,7 @@ function buildBillingDataSchema(translate: TranslateFn) {
99
101
  });
100
102
  }
101
103
 
102
- const isCompany = data.personType === 'pj';
104
+ const isCompany = isCompanyDocument(data.document);
103
105
  const digits = data.document.replace(/\D/g, '');
104
106
 
105
107
  if (digits.length !== (isCompany ? 14 : 11)) {
@@ -113,8 +115,6 @@ function buildBillingDataSchema(translate: TranslateFn) {
113
115
  return;
114
116
  }
115
117
 
116
- /* Dígito verificador, mesmo algoritmo do backend: sem isso 111.111.111-11 passa
117
- * aqui e o PUT da conta é recusado depois, sem o usuário saber o motivo. */
118
118
  const isValid = isCompany ? isValidCNPJ(digits) : isValidCPF(digits);
119
119
  if (!isValid) {
120
120
  ctx.addIssue({
@@ -128,25 +128,13 @@ function buildBillingDataSchema(translate: TranslateFn) {
128
128
  });
129
129
  }
130
130
 
131
- function formatDocument(document: string | null | undefined, isCompany: boolean) {
132
- if (!document) return '';
133
- return isCompany ? formatCNPJ(document) : formatCPF(document);
134
- }
135
-
136
131
  export interface BillingDataFormProps {
137
132
  account: Account | undefined;
138
133
  onSaved: () => void;
139
- /** Fixa o formulário em modo Brasil (documento, CEP e endereço obrigatórios) e esconde o select
140
- * de país — usado por fluxos PIX, que só existem no Brasil. Sem isso, o país vem da conta ou da
141
- * geolocalização do visitante. */
142
134
  country?: 'BR';
143
135
  submitLabel?: string;
144
136
  }
145
137
 
146
- /**
147
- * Formulário de cadastro fiscal/cobrança, reaproveitado por qualquer fluxo que precise dele
148
- * bloqueante (`RequiredBillingDataModal`) ou como etapa dentro de outro modal.
149
- */
150
138
  export function BillingDataForm({ account, onSaved, country, submitLabel }: BillingDataFormProps) {
151
139
  const translate = useTranslations();
152
140
  const { user } = useAuth();
@@ -164,7 +152,6 @@ export function BillingDataForm({ account, onSaved, country, submitLabel }: Bill
164
152
  resolver: zodResolver(schema),
165
153
  mode: 'onChange',
166
154
  defaultValues: {
167
- personType: 'pf',
168
155
  document: '',
169
156
  financialName: '',
170
157
  financialEmail: '',
@@ -189,18 +176,15 @@ export function BillingDataForm({ account, onSaved, country, submitLabel }: Bill
189
176
  formState: { errors, isSubmitting, isValid },
190
177
  } = form;
191
178
 
192
- const personType = watch('personType');
193
- const isCompany = personType === 'pj';
179
+ const isCompany = isCompanyDocument(watch('document'));
194
180
  const isBrazil = watch('country') === 'BR';
195
181
 
196
182
  useEffect(() => {
197
183
  if (!account || isPrefilled.current) return;
198
184
  isPrefilled.current = true;
199
185
 
200
- const accountIsCompany = account.financial_document_type === 2;
201
186
  reset({
202
- personType: accountIsCompany ? 'pj' : 'pf',
203
- document: formatDocument(account.financial_document, accountIsCompany),
187
+ document: formatCPFCNPJ(account.financial_document ?? ''),
204
188
  financialName: account.financial_name ?? '',
205
189
  financialEmail: account.financial_email || user?.email || '',
206
190
  cep: account.zipcode ?? '',
@@ -215,17 +199,7 @@ export function BillingDataForm({ account, onSaved, country, submitLabel }: Bill
215
199
  }, [account, country, reset, user?.email]);
216
200
 
217
201
  const handleDocumentChange = (rawValue: string) => {
218
- setValue('document', isCompany ? formatCNPJ(rawValue) : formatCPF(rawValue), {
219
- shouldValidate: true,
220
- });
221
- };
222
-
223
- const handlePersonTypeChange = (value: 'pf' | 'pj') => {
224
- if (value === personType) return;
225
- setValue('document', '');
226
- /* shouldValidate reroda o resolver: sem isso o `isValid` fica preso no valor
227
- * do documento antigo e o botão continua habilitado com o campo já limpo. */
228
- setValue('personType', value, { shouldValidate: true });
202
+ setValue('document', formatCPFCNPJ(rawValue), { shouldValidate: true });
229
203
  };
230
204
 
231
205
  const handleCepChange = (rawValue: string) => {
@@ -241,16 +215,14 @@ export function BillingDataForm({ account, onSaved, country, submitLabel }: Bill
241
215
  setValue('city', data.localidade, { shouldValidate: true });
242
216
  setValue('state', data.uf, { shouldValidate: true });
243
217
  })
244
- .catch(() => {
245
- // CEP não encontrado: o usuário preenche o endereço na mão.
246
- });
218
+ .catch(() => {});
247
219
  };
248
220
 
249
221
  async function onSubmit(data: BillingDataFormValues) {
250
222
  const payload: UpdateBillingDataRequest =
251
223
  data.country === 'BR'
252
224
  ? {
253
- financial_document_type: data.personType === 'pf' ? 1 : 2,
225
+ financial_document_type: isCompanyDocument(data.document) ? 2 : 1,
254
226
  financial_document: data.document,
255
227
  financial_name: data.financialName,
256
228
  financial_email: data.financialEmail,
@@ -283,8 +255,6 @@ export function BillingDataForm({ account, onSaved, country, submitLabel }: Bill
283
255
  );
284
256
  onSaved();
285
257
  } catch (error) {
286
- /* O backend devolve a razão exata da recusa (ex.: documento fiscal inválido).
287
- * Trocar por um texto genérico deixaria o usuário travado sem saber o que corrigir. */
288
258
  const message =
289
259
  error instanceof Error && error.message
290
260
  ? error.message
@@ -301,8 +271,6 @@ export function BillingDataForm({ account, onSaved, country, submitLabel }: Bill
301
271
  <div className="flex flex-col gap-5">
302
272
  {!country && (
303
273
  <>
304
- {/* Reage ao watch('country'): só quem está aqui dentro sabe quando o usuário
305
- * troca de país no select logo abaixo. */}
306
274
  <span className="paragraph-small-regular text-zinc-600">
307
275
  {isBrazil
308
276
  ? translate('common.billing.requiredData.description')
@@ -329,49 +297,6 @@ export function BillingDataForm({ account, onSaved, country, submitLabel }: Bill
329
297
  </>
330
298
  )}
331
299
 
332
- {isBrazil && (
333
- <div className="flex gap-4">
334
- <Controller
335
- name="personType"
336
- control={control}
337
- render={({ field }) => (
338
- <SelectField
339
- label={translate('common.billing.requiredData.personType')}
340
- placeholder={translate('common.billing.requiredData.select')}
341
- value={field.value}
342
- onChange={(value) => handlePersonTypeChange(value as 'pf' | 'pj')}
343
- options={[
344
- {
345
- value: 'pf',
346
- label: translate('common.billing.requiredData.personTypePf'),
347
- },
348
- {
349
- value: 'pj',
350
- label: translate('common.billing.requiredData.personTypePj'),
351
- },
352
- ]}
353
- containerClassName="flex-1"
354
- />
355
- )}
356
- />
357
- <div className="flex-1">
358
- <FormField
359
- label={
360
- isCompany
361
- ? translate('common.billing.requiredData.cnpj')
362
- : translate('common.billing.requiredData.cpf')
363
- }
364
- placeholder={isCompany ? '__.___.___/____-__' : '___.___.___-__'}
365
- error={!!errors.document}
366
- errorMessage={errors.document?.message}
367
- {...register('document', {
368
- onChange: (event) => handleDocumentChange(event.target.value),
369
- })}
370
- />
371
- </div>
372
- </div>
373
- )}
374
-
375
300
  <FormField
376
301
  label={
377
302
  isBrazil && isCompany
@@ -384,6 +309,18 @@ export function BillingDataForm({ account, onSaved, country, submitLabel }: Bill
384
309
  {...register('financialName')}
385
310
  />
386
311
 
312
+ {isBrazil && (
313
+ <FormField
314
+ label={translate('common.billing.requiredData.document')}
315
+ placeholder={translate('common.billing.requiredData.documentPlaceholder')}
316
+ error={!!errors.document}
317
+ errorMessage={errors.document?.message}
318
+ {...register('document', {
319
+ onChange: (event) => handleDocumentChange(event.target.value),
320
+ })}
321
+ />
322
+ )}
323
+
387
324
  <FormField
388
325
  label={translate('common.billing.requiredData.financialEmail')}
389
326
  placeholder={translate('common.billing.requiredData.financialEmailPlaceholder')}
@@ -1224,11 +1224,8 @@ const messages = {
1224
1224
  addressHeading: 'Billing address',
1225
1225
  select: 'Select',
1226
1226
  typeHere: 'Type here',
1227
- personType: 'Entity type',
1228
- personTypePf: 'Individual',
1229
- personTypePj: 'Company',
1230
- cpf: 'CPF',
1231
- cnpj: 'CNPJ',
1227
+ document: 'CPF or CNPJ',
1228
+ documentPlaceholder: 'Enter your CPF or CNPJ',
1232
1229
  fullName: 'Full name',
1233
1230
  companyName: 'Legal name',
1234
1231
  financialEmail: 'Billing email',
@@ -1252,8 +1249,8 @@ const messages = {
1252
1249
  invalidCep: 'Invalid ZIP code',
1253
1250
  invalidCpf: 'Invalid CPF',
1254
1251
  invalidCnpj: 'Invalid CNPJ',
1255
- invalidCpfChecksum: 'Invalid CPF — check the verification digits',
1256
- invalidCnpjChecksum: 'Invalid CNPJ — check the verification digits',
1252
+ invalidCpfChecksum: 'Invalid CPF, check the verification digits',
1253
+ invalidCnpjChecksum: 'Invalid CNPJ, check the verification digits',
1257
1254
  },
1258
1255
  },
1259
1256
  navigation: {
@@ -1230,11 +1230,8 @@ const messages = {
1230
1230
  addressHeading: 'Dirección de facturación',
1231
1231
  select: 'Selecciona',
1232
1232
  typeHere: 'Escribe aquí',
1233
- personType: 'Tipo de persona',
1234
- personTypePf: 'Persona física',
1235
- personTypePj: 'Persona jurídica',
1236
- cpf: 'CPF',
1237
- cnpj: 'CNPJ',
1233
+ document: 'CPF o CNPJ',
1234
+ documentPlaceholder: 'Escribe tu CPF o CNPJ',
1238
1235
  fullName: 'Nombre completo',
1239
1236
  companyName: 'Razón social',
1240
1237
  financialEmail: 'Correo de facturación',
@@ -1258,8 +1255,8 @@ const messages = {
1258
1255
  invalidCep: 'Código postal inválido',
1259
1256
  invalidCpf: 'CPF inválido',
1260
1257
  invalidCnpj: 'CNPJ inválido',
1261
- invalidCpfChecksum: 'CPF inválido — revisa los dígitos verificadores',
1262
- invalidCnpjChecksum: 'CNPJ inválido — revisa los dígitos verificadores',
1258
+ invalidCpfChecksum: 'CPF inválido, revisa los dígitos verificadores',
1259
+ invalidCnpjChecksum: 'CNPJ inválido, revisa los dígitos verificadores',
1263
1260
  },
1264
1261
  },
1265
1262
  navigation: {
@@ -1241,11 +1241,8 @@ const messages = {
1241
1241
  addressHeading: 'Endereço de cobrança',
1242
1242
  select: 'Selecione',
1243
1243
  typeHere: 'Digite aqui',
1244
- personType: 'Tipo de pessoa',
1245
- personTypePf: 'Pessoa física',
1246
- personTypePj: 'Pessoa jurídica',
1247
- cpf: 'CPF',
1248
- cnpj: 'CNPJ',
1244
+ document: 'CPF ou CNPJ',
1245
+ documentPlaceholder: 'Digite seu CPF ou CNPJ',
1249
1246
  fullName: 'Nome completo',
1250
1247
  companyName: 'Razão social',
1251
1248
  financialEmail: 'E-mail financeiro',
@@ -1269,8 +1266,8 @@ const messages = {
1269
1266
  invalidCep: 'CEP inválido',
1270
1267
  invalidCpf: 'CPF inválido',
1271
1268
  invalidCnpj: 'CNPJ inválido',
1272
- invalidCpfChecksum: 'CPF inválido — confira os dígitos verificadores',
1273
- invalidCnpjChecksum: 'CNPJ inválido — confira os dígitos verificadores',
1269
+ invalidCpfChecksum: 'CPF inválido, confira os dígitos verificadores',
1270
+ invalidCnpjChecksum: 'CNPJ inválido, confira os dígitos verificadores',
1274
1271
  },
1275
1272
  },
1276
1273
 
@@ -71,8 +71,6 @@ const PurchaseIaCreditsPixDataSchema = z.object({
71
71
  export const PurchaseIaCreditsResultSchema = z.object({
72
72
  status: z.number(),
73
73
  message: z.string().optional(),
74
- /** Code estável da recusa (ex.: `FISCAL_DOCUMENT_REQUIRED`) — sem isso o `z.object` faz strip e o
75
- * code do backend morre antes de chegar em quem decide reabrir um passo do fluxo por ele. */
76
74
  code: z.string().optional(),
77
75
  client_secret: z.string().optional(),
78
76
  data: z
@@ -1,14 +1,5 @@
1
- /**
2
- * Whitelabel 1 é a Great por definição — dono do domínio-mãe e o único destino
3
- * seguro quando um host não tem whitelabel própria (ver `WhitelabelService.getDefault`).
4
- */
5
1
  export const DEFAULT_WHITELABEL_ID = 1;
6
2
 
7
- /** Domínio da whitelabel padrão, usado quando `WHITELABEL_DEFAULT_DOMAIN` não está configurada. */
8
- export const DEFAULT_WHITELABEL_DOMAIN = 'greatpages.com.br';
9
-
10
- /** TTL do cache positivo (token resolvido com sucesso) — uma semana. */
11
3
  export const WHITELABEL_CACHE_TTL_SECONDS = 604800;
12
4
 
13
- /** TTL do cache negativo (host sem whitelabel) — curto, pra não travar 5 minutos um domínio novo. */
14
5
  export const MISSING_WHITELABEL_CACHE_TTL_SECONDS = 300;
@@ -1,9 +1,9 @@
1
1
  import greatCache from "@greatapps/cache";
2
2
  import { ApiError } from "../../../infra/api/types";
3
- import { WhitelabelTokenApiResponse, WhitelabelTokenData } from "../schema";
3
+ import { WhitelabelApiResponse, WhitelabelTokenApiResponse, WhitelabelTokenData } from "../schema";
4
4
  import { normalizeHostname } from "../utils/normalize-hostname";
5
5
  import {
6
- DEFAULT_WHITELABEL_DOMAIN,
6
+ DEFAULT_WHITELABEL_ID,
7
7
  MISSING_WHITELABEL_CACHE_TTL_SECONDS,
8
8
  WHITELABEL_CACHE_TTL_SECONDS,
9
9
  } from "../constants/whitelabel.constants";
@@ -50,6 +50,10 @@ class WhitelabelService {
50
50
  return `whitelabel-missing-${hostname}`;
51
51
  }
52
52
 
53
+ private getDefaultCacheKey(): string {
54
+ return `whitelabel-token-id-${DEFAULT_WHITELABEL_ID}`;
55
+ }
56
+
53
57
  private async fetchFromApi(hostname: string): Promise<WhitelabelTokenData | null> {
54
58
  const apiUrl = this.getApiUrl();
55
59
  const whitelabelMasterToken = this.getToken();
@@ -64,11 +68,6 @@ class WhitelabelService {
64
68
  },
65
69
  });
66
70
 
67
- // Host sem whitelabel ativa é caso esperado (domínio novo, subdomínio livre) — não é falha de
68
- // rede, então nem lê o corpo: cai no fallback do whitelabel padrão. 403 NÃO entra aqui: a rota
69
- // emissora nunca responde 403, quem responde é borda (WAF, Access, rate limit) — tratar como
70
- // ausência de whitelabel faria um incidente de borda derrubar toda whitelabel de cliente no
71
- // whitelabel padrão, servindo branding e token de API errados em silêncio.
72
71
  if (response.status === 404) {
73
72
  console.log("[WhitelabelService] No whitelabel for domain", {
74
73
  hostname,
@@ -98,11 +97,6 @@ class WhitelabelService {
98
97
  return result.data[0];
99
98
  }
100
99
 
101
- /**
102
- * Cache (positivo) + fetch + validação de hostname pra um domínio já normalizado. Usado tanto
103
- * pelo host da requisição quanto pelo domínio padrão — sem fallback embutido, pra `getDefault`
104
- * não poder recursar nela mesma.
105
- */
106
100
  private async lookup(hostname: string): Promise<WhitelabelTokenData | null> {
107
101
  const cache = this.createCache();
108
102
  const cacheKey = this.getCacheKey(hostname);
@@ -148,26 +142,49 @@ class WhitelabelService {
148
142
  return data;
149
143
  }
150
144
 
151
- /**
152
- * Whitelabel padrão (Great), destino de todo host sem whitelabel própria. Resolve pelo mesmo
153
- * `lookup`, então herda o cache positivo — mas nunca cai em `getTokenByDomain`, que teria
154
- * fallback: aqui a ausência é falha de plataforma, não caso esperado.
155
- */
156
145
  private async getDefault(): Promise<WhitelabelTokenData> {
157
- const domain = normalizeHostname(
158
- process.env.WHITELABEL_DEFAULT_DOMAIN || DEFAULT_WHITELABEL_DOMAIN,
159
- );
146
+ const cache = this.createCache();
147
+ const cacheKey = this.getDefaultCacheKey();
148
+
149
+ const cachedData = await cache.select(cacheKey);
150
+ if (cachedData.status == 1 && "data" in cachedData && cachedData.data) {
151
+ const cachedWhitelabel: WhitelabelTokenData = JSON.parse(cachedData.data);
152
+ return cachedWhitelabel;
153
+ }
154
+
155
+ const apiUrl = this.getApiUrl();
156
+ const masterToken = this.getToken();
157
+ const url = `${apiUrl}/v1/pt-br/${DEFAULT_WHITELABEL_ID}/whitelabels/${DEFAULT_WHITELABEL_ID}`;
158
+
159
+ const response = await fetch(url, {
160
+ method: "GET",
161
+ headers: {
162
+ authorization: masterToken,
163
+ },
164
+ });
165
+
166
+ if (!response.ok) {
167
+ throw new ApiError(
168
+ `Default whitelabel not found: ${DEFAULT_WHITELABEL_ID}`,
169
+ "WL_DEFAULT_UNAVAILABLE",
170
+ 500,
171
+ );
172
+ }
160
173
 
161
- const data = await this.lookup(domain);
174
+ const result: WhitelabelApiResponse = await response.json();
162
175
 
163
- if (!data) {
176
+ if (result.status !== 1 || !result.data?.length) {
164
177
  throw new ApiError(
165
- `Default whitelabel not found: ${domain}`,
178
+ `Default whitelabel not found: ${DEFAULT_WHITELABEL_ID}`,
166
179
  "WL_DEFAULT_UNAVAILABLE",
167
180
  500,
168
181
  );
169
182
  }
170
183
 
184
+ const data: WhitelabelTokenData = { ...result.data[0], token: masterToken };
185
+
186
+ await cache.insert(cacheKey, JSON.stringify(data), WHITELABEL_CACHE_TTL_SECONDS);
187
+
171
188
  return data;
172
189
  }
173
190
 
@@ -268,6 +285,7 @@ class WhitelabelService {
268
285
 
269
286
  await cache.delete(cacheKey);
270
287
  await cache.delete(missingCacheKey);
288
+ await cache.delete(this.getDefaultCacheKey());
271
289
 
272
290
  const data = await this.getTokenByDomain(hostname);
273
291
 
@@ -26,6 +26,7 @@ export function makeSessionHandlers(opts: SessionHandlersOptions = {}) {
26
26
 
27
27
  return [
28
28
  http.get(`${base}/v1/:locale/:wl/whitelabel/:host/token`, () => apiFind(whitelabel)),
29
+ http.get(`${base}/v1/:locale/:wl/whitelabels/:id`, () => apiFind(whitelabel)),
29
30
  http.get(`${base}/v1/:locale/:wl/tokens`, () => apiFind(whitelabel)),
30
31
  // The users path has more segments, so it must come before /accounts/:acc.
31
32
  http.get(`${base}/v1/:locale/:wl/accounts/:acc/users/:user`, () => apiFind(user)),