@controleonline/ui-common 1.2.102 → 1.2.103
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/package.json
CHANGED
|
@@ -89,6 +89,60 @@ const resolveDownloadHost = ({company = null, appDomain = ''} = {}) => {
|
|
|
89
89
|
* Keep app-domain header only; do not path-inject environment hosts.
|
|
90
90
|
* app-community#432
|
|
91
91
|
*/
|
|
92
|
+
const resolveApiEntrypointHost = () => {
|
|
93
|
+
const entry = normalizeText(APP_ENV?.API_ENTRYPOINT);
|
|
94
|
+
if (!entry) {
|
|
95
|
+
return '';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const withProtocol = /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(entry)
|
|
100
|
+
? entry
|
|
101
|
+
: `https://${entry}`;
|
|
102
|
+
return new URL(withProtocol).host.toLowerCase();
|
|
103
|
+
} catch (error) {
|
|
104
|
+
return entry
|
|
105
|
+
.replace(/^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//, '')
|
|
106
|
+
.replace(/\/.*$/, '')
|
|
107
|
+
.toLowerCase();
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* API host must never be used as /{host}/files/{id}/download segment.
|
|
113
|
+
* Backend File.domain often stores the API host (api.controleonline.com);
|
|
114
|
+
* using it produces broken URLs like:
|
|
115
|
+
* https://api.controleonline.com/api.controleonline.com/files/8408/download
|
|
116
|
+
* Tenant white-labels need:
|
|
117
|
+
* https://api.controleonline.com/erp.jaguncos.com.br/files/8408/download
|
|
118
|
+
* app-community#1160
|
|
119
|
+
*/
|
|
120
|
+
const isApiEntrypointHost = host => {
|
|
121
|
+
const normalized = normalizeText(host).toLowerCase();
|
|
122
|
+
if (!normalized) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const apiHost = resolveApiEntrypointHost();
|
|
127
|
+
if (apiHost && normalized === apiHost) {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (
|
|
132
|
+
normalized === 'api.controleonline.com' ||
|
|
133
|
+
normalized === 'api.controleonline.com.br'
|
|
134
|
+
) {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Generic api.* hosts are infrastructure, not tenant app-domains.
|
|
139
|
+
if (/^api\./i.test(normalized)) {
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return false;
|
|
144
|
+
};
|
|
145
|
+
|
|
92
146
|
const isEnvironmentLikeHost = host => {
|
|
93
147
|
const normalized = normalizeText(host).toLowerCase();
|
|
94
148
|
if (!normalized) return false;
|
|
@@ -120,6 +174,29 @@ const isEnvironmentLikeHost = host => {
|
|
|
120
174
|
return false;
|
|
121
175
|
};
|
|
122
176
|
|
|
177
|
+
/**
|
|
178
|
+
* Prefer File.domain only when it is a real tenant host.
|
|
179
|
+
* Ignore API entrypoint and environment/platform hosts (app-community#1160).
|
|
180
|
+
*/
|
|
181
|
+
const pickDownloadHost = (fileDomain, fallbackHost) => {
|
|
182
|
+
const candidate = normalizeText(fileDomain);
|
|
183
|
+
if (
|
|
184
|
+
candidate &&
|
|
185
|
+
!isApiEntrypointHost(candidate) &&
|
|
186
|
+
!isEnvironmentLikeHost(candidate)
|
|
187
|
+
) {
|
|
188
|
+
return candidate;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const fallback = normalizeText(fallbackHost);
|
|
192
|
+
if (fallback && !isApiEntrypointHost(fallback)) {
|
|
193
|
+
return fallback;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Last resort: runtime host (web multi-tenant) or configured DOMAIN.
|
|
197
|
+
return resolveAppDomain(APP_ENV?.DOMAIN) || fallback;
|
|
198
|
+
};
|
|
199
|
+
|
|
123
200
|
const buildBackendDownloadUrl = (fileId, options = {}) => {
|
|
124
201
|
const normalizedId = String(fileId || '').trim();
|
|
125
202
|
if (!normalizedId) {
|
|
@@ -139,13 +216,11 @@ const buildTenantDownloadUrl = (url, host) => {
|
|
|
139
216
|
return absoluteUrl;
|
|
140
217
|
}
|
|
141
218
|
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
// Do not inject environment hosts into non-download paths (app-community#432).
|
|
148
|
-
if (isEnvironmentLikeHost(normalizedHost) && !isFileDownloadPath) {
|
|
219
|
+
// Do not path-inject environment/platform hosts (app-community#432).
|
|
220
|
+
// Bare /files/{id}/download remains for staging/manager/dev; tenant hosts
|
|
221
|
+
// (erp.jaguncos.com.br, loja..., etc.) still get /{appDomain}/files/... .
|
|
222
|
+
// Never path-inject the API entrypoint host (app-community#1160).
|
|
223
|
+
if (isEnvironmentLikeHost(normalizedHost) || isApiEntrypointHost(normalizedHost)) {
|
|
149
224
|
return absoluteUrl;
|
|
150
225
|
}
|
|
151
226
|
|
|
@@ -310,9 +385,9 @@ const resolveFileSource = (
|
|
|
310
385
|
return null;
|
|
311
386
|
}
|
|
312
387
|
|
|
313
|
-
// Prefer FileService domain
|
|
314
|
-
|
|
315
|
-
const downloadHost =
|
|
388
|
+
// Prefer FileService domain only when it is a tenant host, never the API host.
|
|
389
|
+
// file.domain often comes as api.controleonline.com (app-community#1160).
|
|
390
|
+
const downloadHost = pickDownloadHost(normalizedFile?.domain, host);
|
|
316
391
|
|
|
317
392
|
const uri = isBackendDownload
|
|
318
393
|
? buildTenantDownloadUrl(uriBase, downloadHost)
|
|
@@ -114,4 +114,63 @@ describe('file authentication origin boundary', () => {
|
|
|
114
114
|
const source = resolveDefaultFileSource(1, {headers: {'API-TOKEN': 'session-secret'}});
|
|
115
115
|
expect(source.headers['API-TOKEN']).toBe('session-secret');
|
|
116
116
|
});
|
|
117
|
+
|
|
118
|
+
it('ignores file.domain when it is the API host and uses tenant app-domain (app-community#1160)', () => {
|
|
119
|
+
const company = {domain: 'loja.jaguncos.com.br'};
|
|
120
|
+
|
|
121
|
+
const source = resolveDefaultFileSource(
|
|
122
|
+
{
|
|
123
|
+
id: 8408,
|
|
124
|
+
domain: 'api.controleonline.com',
|
|
125
|
+
url: '/api.controleonline.com/files/8408/download',
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
company,
|
|
129
|
+
headers: {Authorization: 'Bearer token'},
|
|
130
|
+
},
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
expect(source.uri).toBe(
|
|
134
|
+
'https://api.controleonline.com/loja.jaguncos.com.br/files/8408/download',
|
|
135
|
+
);
|
|
136
|
+
expect(source.uri).not.toMatch(/api\.controleonline\.com\/files/);
|
|
137
|
+
expect(source.headers['app-domain']).toBe('loja.jaguncos.com.br');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('ignores API host as company domain and falls back to explicit appDomain', () => {
|
|
141
|
+
const source = resolveDefaultFileSource(
|
|
142
|
+
{
|
|
143
|
+
id: 8408,
|
|
144
|
+
domain: 'api.controleonline.com',
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
company: {domain: 'api.controleonline.com'},
|
|
148
|
+
appDomain: 'erp.jaguncos.com.br',
|
|
149
|
+
headers: {},
|
|
150
|
+
},
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
expect(source.uri).toBe(
|
|
154
|
+
'https://api.controleonline.com/erp.jaguncos.com.br/files/8408/download',
|
|
155
|
+
);
|
|
156
|
+
expect(source.headers['app-domain']).toBe('erp.jaguncos.com.br');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('still prefers a real tenant file.domain over company when not API host', () => {
|
|
160
|
+
const source = resolveDefaultFileSource(
|
|
161
|
+
{
|
|
162
|
+
id: 12,
|
|
163
|
+
domain: 'cardapio.jaguncos.com.br',
|
|
164
|
+
url: '/files/12/download',
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
company: {domain: 'erp.jaguncos.com.br'},
|
|
168
|
+
},
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
expect(source.uri).toBe(
|
|
172
|
+
'https://api.controleonline.com/cardapio.jaguncos.com.br/files/12/download',
|
|
173
|
+
);
|
|
174
|
+
expect(source.headers['app-domain']).toBe('cardapio.jaguncos.com.br');
|
|
175
|
+
});
|
|
117
176
|
});
|