@greatapps/common 1.1.795 → 1.1.796
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.
- package/dist/components/modals/billing/BillingDataForm.mjs +406 -0
- package/dist/components/modals/billing/BillingDataForm.mjs.map +1 -0
- package/dist/components/modals/billing/RequiredBillingDataModal.mjs +5 -405
- package/dist/components/modals/billing/RequiredBillingDataModal.mjs.map +1 -1
- package/dist/i18n/resolve-locale.mjs +1 -1
- package/dist/i18n/resolve-locale.mjs.map +1 -1
- package/dist/index.mjs +4 -0
- package/dist/index.mjs.map +1 -1
- package/dist/modules/ia-credits/types/ai-credits-offer.type.mjs +3 -0
- package/dist/modules/ia-credits/types/ai-credits-offer.type.mjs.map +1 -1
- package/dist/modules/whitelabel/actions/find-whitelabel.action.mjs +1 -5
- package/dist/modules/whitelabel/actions/find-whitelabel.action.mjs.map +1 -1
- package/dist/modules/whitelabel/constants/whitelabel.constants.mjs +11 -0
- package/dist/modules/whitelabel/constants/whitelabel.constants.mjs.map +1 -0
- package/dist/modules/whitelabel/services/whitelabel.service.mjs +69 -11
- package/dist/modules/whitelabel/services/whitelabel.service.mjs.map +1 -1
- package/package.json +10 -9
- package/src/components/modals/billing/BillingDataForm.tsx +506 -0
- package/src/components/modals/billing/RequiredBillingDataModal.tsx +3 -475
- package/src/i18n/resolve-locale.ts +1 -1
- package/src/index.ts +3 -0
- package/src/modules/ia-credits/types/ai-credits-offer.type.ts +3 -0
- package/src/modules/whitelabel/actions/find-whitelabel.action.ts +1 -7
- package/src/modules/whitelabel/constants/whitelabel.constants.ts +14 -0
- package/src/modules/whitelabel/services/whitelabel.service.ts +282 -209
|
@@ -1,209 +1,282 @@
|
|
|
1
|
-
import greatCache from "@greatapps/cache";
|
|
2
|
-
import { ApiError } from "../../../infra/api/types";
|
|
3
|
-
import { WhitelabelTokenApiResponse, WhitelabelTokenData } from "../schema";
|
|
4
|
-
import { normalizeHostname } from "../utils/normalize-hostname";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
1
|
+
import greatCache from "@greatapps/cache";
|
|
2
|
+
import { ApiError } from "../../../infra/api/types";
|
|
3
|
+
import { WhitelabelTokenApiResponse, WhitelabelTokenData } from "../schema";
|
|
4
|
+
import { normalizeHostname } from "../utils/normalize-hostname";
|
|
5
|
+
import {
|
|
6
|
+
DEFAULT_WHITELABEL_DOMAIN,
|
|
7
|
+
MISSING_WHITELABEL_CACHE_TTL_SECONDS,
|
|
8
|
+
WHITELABEL_CACHE_TTL_SECONDS,
|
|
9
|
+
} from "../constants/whitelabel.constants";
|
|
10
|
+
|
|
11
|
+
class WhitelabelService {
|
|
12
|
+
private getApiUrl(): string {
|
|
13
|
+
const apiUrl = process.env.GAPPS_R3_API_URL;
|
|
14
|
+
if (!apiUrl) {
|
|
15
|
+
throw new ApiError(
|
|
16
|
+
"GAPPS_R3_API_URL not configured",
|
|
17
|
+
"CONFIG_ERROR",
|
|
18
|
+
500,
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
return apiUrl;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
private getToken(): string {
|
|
25
|
+
const token = process.env.WHITELABEL_TOKEN_MASTER;
|
|
26
|
+
if (!token) {
|
|
27
|
+
throw new ApiError(
|
|
28
|
+
"WHITELABEL_TOKEN_MASTER not configured",
|
|
29
|
+
"CONFIG_ERROR",
|
|
30
|
+
500,
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
return token;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private createCache() {
|
|
37
|
+
return new greatCache({
|
|
38
|
+
service: "whitelabel-service",
|
|
39
|
+
version: "1.0.3",
|
|
40
|
+
domain: "whitelabel-cache.greatapps.com.br",
|
|
41
|
+
ambient: process.env.NODE_ENV || "development",
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private getCacheKey(hostname: string): string {
|
|
46
|
+
return `whitelabel-token-${hostname}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private getMissingCacheKey(hostname: string): string {
|
|
50
|
+
return `whitelabel-missing-${hostname}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private async fetchFromApi(hostname: string): Promise<WhitelabelTokenData | null> {
|
|
54
|
+
const apiUrl = this.getApiUrl();
|
|
55
|
+
const whitelabelMasterToken = this.getToken();
|
|
56
|
+
const url = `${apiUrl}/v1/pt-br/1/whitelabel/${hostname}/token`;
|
|
57
|
+
|
|
58
|
+
console.log("[WhitelabelService] Fetching token for domain", { url });
|
|
59
|
+
|
|
60
|
+
const response = await fetch(url, {
|
|
61
|
+
method: "GET",
|
|
62
|
+
headers: {
|
|
63
|
+
authorization: whitelabelMasterToken,
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
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
|
+
if (response.status === 404) {
|
|
73
|
+
console.log("[WhitelabelService] No whitelabel for domain", {
|
|
74
|
+
hostname,
|
|
75
|
+
status: response.status,
|
|
76
|
+
});
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
console.error("[WhitelabelService] Failed to fetch whitelabel token", {
|
|
82
|
+
hostname,
|
|
83
|
+
status: response.status,
|
|
84
|
+
});
|
|
85
|
+
throw new ApiError(
|
|
86
|
+
`Failed to fetch whitelabel token: ${response.status}`,
|
|
87
|
+
"FETCH_ERROR",
|
|
88
|
+
response.status,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const result: WhitelabelTokenApiResponse = await response.json();
|
|
93
|
+
|
|
94
|
+
if (result.status !== 1 || !result.data?.length) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return result.data[0];
|
|
99
|
+
}
|
|
100
|
+
|
|
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
|
+
private async lookup(hostname: string): Promise<WhitelabelTokenData | null> {
|
|
107
|
+
const cache = this.createCache();
|
|
108
|
+
const cacheKey = this.getCacheKey(hostname);
|
|
109
|
+
|
|
110
|
+
const cachedData = await cache.select(cacheKey);
|
|
111
|
+
console.debug("[WhitelabelService] Cache lookup for domain", {
|
|
112
|
+
cacheKey,
|
|
113
|
+
cachedData,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
if (cachedData.status == 1 && "data" in cachedData && cachedData.data) {
|
|
117
|
+
const cachedWhitelabel: WhitelabelTokenData = JSON.parse(cachedData.data);
|
|
118
|
+
|
|
119
|
+
if (this.#matchesHostname(cachedWhitelabel, hostname)) {
|
|
120
|
+
console.log("[WhitelabelService] Cache hit for domain", { hostname });
|
|
121
|
+
return cachedWhitelabel;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
console.error("[WhitelabelService] Cached whitelabel does not match hostname", {
|
|
125
|
+
hostname,
|
|
126
|
+
cacheKey,
|
|
127
|
+
whitelabelId: cachedWhitelabel.id,
|
|
128
|
+
whitelabelDomain: cachedWhitelabel.domain,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
await cache.delete(cacheKey);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const data = await this.fetchFromApi(hostname);
|
|
135
|
+
if (!data) return null;
|
|
136
|
+
|
|
137
|
+
if (!this.#matchesHostname(data, hostname)) {
|
|
138
|
+
console.error("[WhitelabelService] API returned whitelabel of another hostname", {
|
|
139
|
+
hostname,
|
|
140
|
+
whitelabelId: data.id,
|
|
141
|
+
whitelabelDomain: data.domain,
|
|
142
|
+
});
|
|
143
|
+
return data;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
await cache.insert(cacheKey, JSON.stringify(data), WHITELABEL_CACHE_TTL_SECONDS);
|
|
147
|
+
|
|
148
|
+
return data;
|
|
149
|
+
}
|
|
150
|
+
|
|
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
|
+
private async getDefault(): Promise<WhitelabelTokenData> {
|
|
157
|
+
const domain = normalizeHostname(
|
|
158
|
+
process.env.WHITELABEL_DEFAULT_DOMAIN || DEFAULT_WHITELABEL_DOMAIN,
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
const data = await this.lookup(domain);
|
|
162
|
+
|
|
163
|
+
if (!data) {
|
|
164
|
+
throw new ApiError(
|
|
165
|
+
`Default whitelabel not found: ${domain}`,
|
|
166
|
+
"WL_DEFAULT_UNAVAILABLE",
|
|
167
|
+
500,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return data;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async getTokenByDomain(hostname: string): Promise<WhitelabelTokenData> {
|
|
175
|
+
hostname = `${normalizeHostname(process.env.WHITELABEL_DOMAIN || hostname)}`;
|
|
176
|
+
console.debug("[WhitelabelService] Getting token for domain", {
|
|
177
|
+
hostname,
|
|
178
|
+
byEnv: process.env.WHITELABEL_DOMAIN,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const cache = this.createCache();
|
|
182
|
+
const missingCacheKey = this.getMissingCacheKey(hostname);
|
|
183
|
+
|
|
184
|
+
const cachedMissing = await cache.select(missingCacheKey);
|
|
185
|
+
if (cachedMissing.status == 1) {
|
|
186
|
+
console.log("[WhitelabelService] Cached miss for domain, using default", { hostname });
|
|
187
|
+
return this.getDefault();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const data = await this.lookup(hostname);
|
|
191
|
+
if (data) return data;
|
|
192
|
+
|
|
193
|
+
console.log("[WhitelabelService] No whitelabel for domain, falling back to default", {
|
|
194
|
+
hostname,
|
|
195
|
+
});
|
|
196
|
+
await cache.insert(missingCacheKey, "1", MISSING_WHITELABEL_CACHE_TTL_SECONDS);
|
|
197
|
+
|
|
198
|
+
return this.getDefault();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
#matchesHostname(data: WhitelabelTokenData, hostname: string): boolean {
|
|
202
|
+
if (!data.domain) return true;
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
return normalizeHostname(new URL(data.domain).hostname) === hostname;
|
|
206
|
+
} catch {
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async getTokenByWhitelabelId(idWl: number): Promise<string> {
|
|
212
|
+
const apiUrl = this.getApiUrl();
|
|
213
|
+
const masterToken = this.getToken();
|
|
214
|
+
const url = `${apiUrl}/v1/pt-br/${idWl}/tokens?limit=1&page=1&sort=id:desc`;
|
|
215
|
+
console.debug("[WhitelabelService] Fetching token by whitelabel ID", {
|
|
216
|
+
url,
|
|
217
|
+
config: {
|
|
218
|
+
method: "GET",
|
|
219
|
+
headers: { authorization: masterToken },
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const response = await fetch(url, {
|
|
224
|
+
method: "GET",
|
|
225
|
+
headers: { authorization: masterToken },
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
console.debug("[WhitelabelService] Response received for whitelabel ID", {
|
|
229
|
+
response,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
if (!response.ok) {
|
|
233
|
+
throw new ApiError(
|
|
234
|
+
`Failed to fetch whitelabel token: ${response.status}`,
|
|
235
|
+
"WL_TOKEN_NOT_FOUND",
|
|
236
|
+
response.status,
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const result: WhitelabelTokenApiResponse = await response.json();
|
|
241
|
+
|
|
242
|
+
if (result.status !== 1 || !result.data?.length) {
|
|
243
|
+
throw new ApiError(
|
|
244
|
+
"Token do whitelabel não encontrado",
|
|
245
|
+
"WL_TOKEN_NOT_FOUND",
|
|
246
|
+
404,
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return result.data[0].token;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async revalidateByDomain(hostname: string): Promise<WhitelabelTokenData> {
|
|
254
|
+
hostname = `${normalizeHostname(process.env.WHITELABEL_DOMAIN || hostname)}`;
|
|
255
|
+
console.debug("[WhitelabelService] Getting token for domain", {
|
|
256
|
+
hostname,
|
|
257
|
+
byEnv: process.env.WHITELABEL_DOMAIN,
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
const cache = this.createCache();
|
|
261
|
+
const cacheKey = this.getCacheKey(hostname);
|
|
262
|
+
const missingCacheKey = this.getMissingCacheKey(hostname);
|
|
263
|
+
|
|
264
|
+
console.log("[WhitelabelService] Revalidating cache for domain", {
|
|
265
|
+
hostname,
|
|
266
|
+
cacheKey,
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
await cache.delete(cacheKey);
|
|
270
|
+
await cache.delete(missingCacheKey);
|
|
271
|
+
|
|
272
|
+
const data = await this.getTokenByDomain(hostname);
|
|
273
|
+
|
|
274
|
+
console.log("[WhitelabelService] Cache revalidated for domain", {
|
|
275
|
+
hostname,
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
return data;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export const whitelabelService = new WhitelabelService();
|