@web-portal/core-infrastructure 1.0.0 → 1.0.2
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/fesm2022/web-portal-core-infrastructure.mjs +438 -12
- package/fesm2022/web-portal-core-infrastructure.mjs.map +1 -1
- package/lib/services/file-aia-gcs.service.d.ts +21 -0
- package/lib/services/file-gcs.service.d.ts +21 -0
- package/lib/services/file-wap-gcs.service.d.ts +7 -1
- package/package.json +1 -1
|
@@ -1687,6 +1687,9 @@ class FileGCSService {
|
|
|
1687
1687
|
getSignedLink(filePath) {
|
|
1688
1688
|
return this._apiService.post(`${this.baseUrl}/getSignedLink?filePath=${filePath}`);
|
|
1689
1689
|
}
|
|
1690
|
+
getPresignedLink(fileName) {
|
|
1691
|
+
return this._apiService.post(`${this.baseUrl}/getPresignedLink?fileName=${fileName}`);
|
|
1692
|
+
}
|
|
1690
1693
|
attachFile(file) {
|
|
1691
1694
|
const formData = new FormData();
|
|
1692
1695
|
formData.append("file", file);
|
|
@@ -1705,6 +1708,195 @@ class FileGCSService {
|
|
|
1705
1708
|
});
|
|
1706
1709
|
return firstValueFrom(this._http.request(req));
|
|
1707
1710
|
}
|
|
1711
|
+
/**
|
|
1712
|
+
* Build an Error object but keep HTTP-related metadata for callers that need to branch by status.
|
|
1713
|
+
* Note: We intentionally do NOT change business flow; we only enrich the error instance.
|
|
1714
|
+
*/
|
|
1715
|
+
buildHttpError(message, meta) {
|
|
1716
|
+
const err = new Error(message);
|
|
1717
|
+
if (meta?.status !== undefined)
|
|
1718
|
+
err.status = meta.status;
|
|
1719
|
+
if (meta?.statusText !== undefined)
|
|
1720
|
+
err.statusText = meta.statusText;
|
|
1721
|
+
if (meta?.url !== undefined)
|
|
1722
|
+
err.url = meta.url;
|
|
1723
|
+
if (meta?.cause !== undefined)
|
|
1724
|
+
err.cause = meta.cause;
|
|
1725
|
+
return err;
|
|
1726
|
+
}
|
|
1727
|
+
uploadFile(file) {
|
|
1728
|
+
return new Observable((observer) => {
|
|
1729
|
+
let xhr = null;
|
|
1730
|
+
// Tạo tên file unique để tránh trùng tên khi upload nhiều lần
|
|
1731
|
+
const uniqueFileName = this.generateUniqueFileName(file.name);
|
|
1732
|
+
firstValueFrom(this.getPresignedLink(uniqueFileName))
|
|
1733
|
+
.then((presignedData) => {
|
|
1734
|
+
if (!presignedData) {
|
|
1735
|
+
throw new Error("Không nhận được presigned link từ server");
|
|
1736
|
+
}
|
|
1737
|
+
const presignedURL = presignedData.PreSignedURL ||
|
|
1738
|
+
presignedData.preSignedURL ||
|
|
1739
|
+
presignedData.url;
|
|
1740
|
+
const filePath = presignedData.FilePath || presignedData.filePath;
|
|
1741
|
+
const absoluteURL = presignedData.AbsoluteURL || presignedData.absoluteURL;
|
|
1742
|
+
if (!presignedURL) {
|
|
1743
|
+
throw new Error("Presigned URL không hợp lệ");
|
|
1744
|
+
}
|
|
1745
|
+
const contentType = presignedData.ContentType ||
|
|
1746
|
+
presignedData.contentType ||
|
|
1747
|
+
file.type ||
|
|
1748
|
+
"application/octet-stream";
|
|
1749
|
+
xhr = new XMLHttpRequest();
|
|
1750
|
+
const cleanup = () => {
|
|
1751
|
+
if (xhr) {
|
|
1752
|
+
xhr.upload.removeEventListener("progress", progressHandler);
|
|
1753
|
+
xhr.removeEventListener("load", loadHandler);
|
|
1754
|
+
xhr.removeEventListener("error", errorHandler);
|
|
1755
|
+
xhr.removeEventListener("timeout", timeoutHandler);
|
|
1756
|
+
xhr.abort();
|
|
1757
|
+
xhr = null;
|
|
1758
|
+
}
|
|
1759
|
+
};
|
|
1760
|
+
const progressHandler = (_event) => {
|
|
1761
|
+
// No-op: progress not consumed by current callers
|
|
1762
|
+
};
|
|
1763
|
+
const loadHandler = async () => {
|
|
1764
|
+
if (xhr && xhr.status === 200) {
|
|
1765
|
+
try {
|
|
1766
|
+
const finalUrl = absoluteURL || presignedURL;
|
|
1767
|
+
const result = this.buildResult(file, filePath, finalUrl);
|
|
1768
|
+
cleanup();
|
|
1769
|
+
observer.next(result);
|
|
1770
|
+
observer.complete();
|
|
1771
|
+
}
|
|
1772
|
+
catch (err) {
|
|
1773
|
+
cleanup();
|
|
1774
|
+
const msg = err?.message ||
|
|
1775
|
+
"Upload thất bại. Vui lòng thử lại.";
|
|
1776
|
+
observer.error(this.buildHttpError(msg, {
|
|
1777
|
+
status: xhr?.status,
|
|
1778
|
+
statusText: xhr?.statusText,
|
|
1779
|
+
url: presignedURL,
|
|
1780
|
+
cause: err,
|
|
1781
|
+
}));
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
else if (xhr) {
|
|
1785
|
+
const errorMessage = `Upload file thất bại: ${xhr.statusText} (${xhr.status})`;
|
|
1786
|
+
cleanup();
|
|
1787
|
+
observer.error(this.buildHttpError(errorMessage, {
|
|
1788
|
+
status: xhr.status,
|
|
1789
|
+
statusText: xhr.statusText,
|
|
1790
|
+
url: presignedURL,
|
|
1791
|
+
}));
|
|
1792
|
+
}
|
|
1793
|
+
};
|
|
1794
|
+
const errorHandler = () => {
|
|
1795
|
+
if (!xhr)
|
|
1796
|
+
return;
|
|
1797
|
+
let userMessage = "Upload file thất bại";
|
|
1798
|
+
if (xhr.status === 0) {
|
|
1799
|
+
userMessage =
|
|
1800
|
+
"Không thể kết nối đến máy chủ lưu trữ. Máy chủ không phản hồi.";
|
|
1801
|
+
}
|
|
1802
|
+
else if (xhr.status >= 500) {
|
|
1803
|
+
userMessage = "Lỗi máy chủ. Vui lòng thử lại sau.";
|
|
1804
|
+
}
|
|
1805
|
+
else if (xhr.status >= 400) {
|
|
1806
|
+
userMessage = `Lỗi yêu cầu: ${xhr.statusText}`;
|
|
1807
|
+
}
|
|
1808
|
+
cleanup();
|
|
1809
|
+
observer.error(this.buildHttpError(userMessage, {
|
|
1810
|
+
status: xhr.status,
|
|
1811
|
+
statusText: xhr.statusText,
|
|
1812
|
+
url: presignedURL,
|
|
1813
|
+
}));
|
|
1814
|
+
};
|
|
1815
|
+
const timeoutHandler = () => {
|
|
1816
|
+
cleanup();
|
|
1817
|
+
observer.error(this.buildHttpError("Kết nối đến máy chủ hết thời gian. Vui lòng thử lại.", {
|
|
1818
|
+
status: xhr?.status,
|
|
1819
|
+
statusText: xhr?.statusText,
|
|
1820
|
+
url: presignedURL,
|
|
1821
|
+
}));
|
|
1822
|
+
};
|
|
1823
|
+
xhr.upload.addEventListener("progress", progressHandler);
|
|
1824
|
+
xhr.addEventListener("load", loadHandler);
|
|
1825
|
+
xhr.addEventListener("error", errorHandler);
|
|
1826
|
+
xhr.addEventListener("timeout", timeoutHandler);
|
|
1827
|
+
xhr.timeout = 60000 * 5;
|
|
1828
|
+
xhr.open("PUT", presignedURL, true);
|
|
1829
|
+
xhr.setRequestHeader("Content-Type", contentType);
|
|
1830
|
+
xhr.send(file);
|
|
1831
|
+
})
|
|
1832
|
+
.catch((error) => {
|
|
1833
|
+
if (xhr) {
|
|
1834
|
+
xhr.abort();
|
|
1835
|
+
xhr = null;
|
|
1836
|
+
}
|
|
1837
|
+
let userMessage = "Upload file thất bại";
|
|
1838
|
+
if (error.errorMessage) {
|
|
1839
|
+
userMessage = error.errorMessage;
|
|
1840
|
+
}
|
|
1841
|
+
else if (error.code === "ECONNABORTED" ||
|
|
1842
|
+
error.message?.includes("timeout")) {
|
|
1843
|
+
userMessage =
|
|
1844
|
+
"Kết nối đến máy chủ hết thời gian. Vui lòng thử lại.";
|
|
1845
|
+
}
|
|
1846
|
+
else if (error.message?.includes("failed to respond") ||
|
|
1847
|
+
error.message?.includes("Network Error")) {
|
|
1848
|
+
userMessage =
|
|
1849
|
+
"Không thể kết nối đến máy chủ lưu trữ. Máy chủ không phản hồi.";
|
|
1850
|
+
}
|
|
1851
|
+
else if (error.message) {
|
|
1852
|
+
userMessage = `Lỗi kết nối: ${error.message}`;
|
|
1853
|
+
}
|
|
1854
|
+
else if (error.error?.message) {
|
|
1855
|
+
userMessage = error.error.message;
|
|
1856
|
+
}
|
|
1857
|
+
observer.error(this.buildHttpError(userMessage, {
|
|
1858
|
+
status: error?.status,
|
|
1859
|
+
statusText: error?.statusText,
|
|
1860
|
+
url: error?.url,
|
|
1861
|
+
cause: error,
|
|
1862
|
+
}));
|
|
1863
|
+
});
|
|
1864
|
+
return () => {
|
|
1865
|
+
if (xhr) {
|
|
1866
|
+
xhr.abort();
|
|
1867
|
+
xhr = null;
|
|
1868
|
+
}
|
|
1869
|
+
};
|
|
1870
|
+
});
|
|
1871
|
+
}
|
|
1872
|
+
/**
|
|
1873
|
+
* Tạo tên file unique bằng cách thêm timestamp và random string
|
|
1874
|
+
* Format: originalname-timestamp-random.ext
|
|
1875
|
+
*/
|
|
1876
|
+
generateUniqueFileName(originalFileName) {
|
|
1877
|
+
const timestamp = Date.now();
|
|
1878
|
+
const randomString = Math.random().toString(36).substring(2, 9);
|
|
1879
|
+
// Tách tên file và extension
|
|
1880
|
+
const lastDotIndex = originalFileName.lastIndexOf(".");
|
|
1881
|
+
if (lastDotIndex === -1) {
|
|
1882
|
+
// Không có extension
|
|
1883
|
+
return `${originalFileName}-${timestamp}-${randomString}`;
|
|
1884
|
+
}
|
|
1885
|
+
const nameWithoutExt = originalFileName.substring(0, lastDotIndex);
|
|
1886
|
+
const extension = originalFileName.substring(lastDotIndex);
|
|
1887
|
+
return `${nameWithoutExt}-${timestamp}-${randomString}${extension}`;
|
|
1888
|
+
}
|
|
1889
|
+
extractFileName(filePath) {
|
|
1890
|
+
return filePath.split("/").pop() || "";
|
|
1891
|
+
}
|
|
1892
|
+
buildResult(file, filePath, url) {
|
|
1893
|
+
return {
|
|
1894
|
+
url,
|
|
1895
|
+
filePath,
|
|
1896
|
+
fileName: this.extractFileName(filePath) || file.name,
|
|
1897
|
+
originalName: file.name,
|
|
1898
|
+
};
|
|
1899
|
+
}
|
|
1708
1900
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: FileGCSService, deps: [{ token: i1$2.HttpClient }, { token: ApiService }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1709
1901
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: FileGCSService, providedIn: "root" }); }
|
|
1710
1902
|
}
|
|
@@ -1722,6 +1914,9 @@ class FileAIA_GCSService {
|
|
|
1722
1914
|
getSignedLink(filePath) {
|
|
1723
1915
|
return this._apiService.post(`${this.baseUrl}/getSignedLink?filePath=${filePath}`);
|
|
1724
1916
|
}
|
|
1917
|
+
getPresignedLink(fileName) {
|
|
1918
|
+
return this._apiService.post(`${this.baseUrl}/getPresignedLink?fileName=${fileName}`);
|
|
1919
|
+
}
|
|
1725
1920
|
attachFile(file) {
|
|
1726
1921
|
const formData = new FormData();
|
|
1727
1922
|
formData.append("file", file);
|
|
@@ -1740,6 +1935,195 @@ class FileAIA_GCSService {
|
|
|
1740
1935
|
});
|
|
1741
1936
|
return firstValueFrom(this._http.request(req));
|
|
1742
1937
|
}
|
|
1938
|
+
/**
|
|
1939
|
+
* Build an Error object but keep HTTP-related metadata for callers that need to branch by status.
|
|
1940
|
+
* Note: We intentionally do NOT change business flow; we only enrich the error instance.
|
|
1941
|
+
*/
|
|
1942
|
+
buildHttpError(message, meta) {
|
|
1943
|
+
const err = new Error(message);
|
|
1944
|
+
if (meta?.status !== undefined)
|
|
1945
|
+
err.status = meta.status;
|
|
1946
|
+
if (meta?.statusText !== undefined)
|
|
1947
|
+
err.statusText = meta.statusText;
|
|
1948
|
+
if (meta?.url !== undefined)
|
|
1949
|
+
err.url = meta.url;
|
|
1950
|
+
if (meta?.cause !== undefined)
|
|
1951
|
+
err.cause = meta.cause;
|
|
1952
|
+
return err;
|
|
1953
|
+
}
|
|
1954
|
+
uploadFile(file) {
|
|
1955
|
+
return new Observable((observer) => {
|
|
1956
|
+
let xhr = null;
|
|
1957
|
+
// Tạo tên file unique để tránh trùng tên khi upload nhiều lần
|
|
1958
|
+
const uniqueFileName = this.generateUniqueFileName(file.name);
|
|
1959
|
+
firstValueFrom(this.getPresignedLink(uniqueFileName))
|
|
1960
|
+
.then((presignedData) => {
|
|
1961
|
+
if (!presignedData) {
|
|
1962
|
+
throw new Error("Không nhận được presigned link từ server");
|
|
1963
|
+
}
|
|
1964
|
+
const presignedURL = presignedData.PreSignedURL ||
|
|
1965
|
+
presignedData.preSignedURL ||
|
|
1966
|
+
presignedData.url;
|
|
1967
|
+
const filePath = presignedData.FilePath || presignedData.filePath;
|
|
1968
|
+
const absoluteURL = presignedData.AbsoluteURL || presignedData.absoluteURL;
|
|
1969
|
+
if (!presignedURL) {
|
|
1970
|
+
throw new Error("Presigned URL không hợp lệ");
|
|
1971
|
+
}
|
|
1972
|
+
const contentType = presignedData.ContentType ||
|
|
1973
|
+
presignedData.contentType ||
|
|
1974
|
+
file.type ||
|
|
1975
|
+
"application/octet-stream";
|
|
1976
|
+
xhr = new XMLHttpRequest();
|
|
1977
|
+
const cleanup = () => {
|
|
1978
|
+
if (xhr) {
|
|
1979
|
+
xhr.upload.removeEventListener("progress", progressHandler);
|
|
1980
|
+
xhr.removeEventListener("load", loadHandler);
|
|
1981
|
+
xhr.removeEventListener("error", errorHandler);
|
|
1982
|
+
xhr.removeEventListener("timeout", timeoutHandler);
|
|
1983
|
+
xhr.abort();
|
|
1984
|
+
xhr = null;
|
|
1985
|
+
}
|
|
1986
|
+
};
|
|
1987
|
+
const progressHandler = (_event) => {
|
|
1988
|
+
// No-op: progress not consumed by current callers
|
|
1989
|
+
};
|
|
1990
|
+
const loadHandler = async () => {
|
|
1991
|
+
if (xhr && xhr.status === 200) {
|
|
1992
|
+
try {
|
|
1993
|
+
const finalUrl = absoluteURL || presignedURL;
|
|
1994
|
+
const result = this.buildResult(file, filePath, finalUrl);
|
|
1995
|
+
cleanup();
|
|
1996
|
+
observer.next(result);
|
|
1997
|
+
observer.complete();
|
|
1998
|
+
}
|
|
1999
|
+
catch (err) {
|
|
2000
|
+
cleanup();
|
|
2001
|
+
const msg = err?.message ||
|
|
2002
|
+
"Upload thất bại. Vui lòng thử lại.";
|
|
2003
|
+
observer.error(this.buildHttpError(msg, {
|
|
2004
|
+
status: xhr?.status,
|
|
2005
|
+
statusText: xhr?.statusText,
|
|
2006
|
+
url: presignedURL,
|
|
2007
|
+
cause: err,
|
|
2008
|
+
}));
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
else if (xhr) {
|
|
2012
|
+
const errorMessage = `Upload file thất bại: ${xhr.statusText} (${xhr.status})`;
|
|
2013
|
+
cleanup();
|
|
2014
|
+
observer.error(this.buildHttpError(errorMessage, {
|
|
2015
|
+
status: xhr.status,
|
|
2016
|
+
statusText: xhr.statusText,
|
|
2017
|
+
url: presignedURL,
|
|
2018
|
+
}));
|
|
2019
|
+
}
|
|
2020
|
+
};
|
|
2021
|
+
const errorHandler = () => {
|
|
2022
|
+
if (!xhr)
|
|
2023
|
+
return;
|
|
2024
|
+
let userMessage = "Upload file thất bại";
|
|
2025
|
+
if (xhr.status === 0) {
|
|
2026
|
+
userMessage =
|
|
2027
|
+
"Không thể kết nối đến máy chủ lưu trữ. Máy chủ không phản hồi.";
|
|
2028
|
+
}
|
|
2029
|
+
else if (xhr.status >= 500) {
|
|
2030
|
+
userMessage = "Lỗi máy chủ. Vui lòng thử lại sau.";
|
|
2031
|
+
}
|
|
2032
|
+
else if (xhr.status >= 400) {
|
|
2033
|
+
userMessage = `Lỗi yêu cầu: ${xhr.statusText}`;
|
|
2034
|
+
}
|
|
2035
|
+
cleanup();
|
|
2036
|
+
observer.error(this.buildHttpError(userMessage, {
|
|
2037
|
+
status: xhr.status,
|
|
2038
|
+
statusText: xhr.statusText,
|
|
2039
|
+
url: presignedURL,
|
|
2040
|
+
}));
|
|
2041
|
+
};
|
|
2042
|
+
const timeoutHandler = () => {
|
|
2043
|
+
cleanup();
|
|
2044
|
+
observer.error(this.buildHttpError("Kết nối đến máy chủ hết thời gian. Vui lòng thử lại.", {
|
|
2045
|
+
status: xhr?.status,
|
|
2046
|
+
statusText: xhr?.statusText,
|
|
2047
|
+
url: presignedURL,
|
|
2048
|
+
}));
|
|
2049
|
+
};
|
|
2050
|
+
xhr.upload.addEventListener("progress", progressHandler);
|
|
2051
|
+
xhr.addEventListener("load", loadHandler);
|
|
2052
|
+
xhr.addEventListener("error", errorHandler);
|
|
2053
|
+
xhr.addEventListener("timeout", timeoutHandler);
|
|
2054
|
+
xhr.timeout = 60000 * 5;
|
|
2055
|
+
xhr.open("PUT", presignedURL, true);
|
|
2056
|
+
xhr.setRequestHeader("Content-Type", contentType);
|
|
2057
|
+
xhr.send(file);
|
|
2058
|
+
})
|
|
2059
|
+
.catch((error) => {
|
|
2060
|
+
if (xhr) {
|
|
2061
|
+
xhr.abort();
|
|
2062
|
+
xhr = null;
|
|
2063
|
+
}
|
|
2064
|
+
let userMessage = "Upload file thất bại";
|
|
2065
|
+
if (error.errorMessage) {
|
|
2066
|
+
userMessage = error.errorMessage;
|
|
2067
|
+
}
|
|
2068
|
+
else if (error.code === "ECONNABORTED" ||
|
|
2069
|
+
error.message?.includes("timeout")) {
|
|
2070
|
+
userMessage =
|
|
2071
|
+
"Kết nối đến máy chủ hết thời gian. Vui lòng thử lại.";
|
|
2072
|
+
}
|
|
2073
|
+
else if (error.message?.includes("failed to respond") ||
|
|
2074
|
+
error.message?.includes("Network Error")) {
|
|
2075
|
+
userMessage =
|
|
2076
|
+
"Không thể kết nối đến máy chủ lưu trữ. Máy chủ không phản hồi.";
|
|
2077
|
+
}
|
|
2078
|
+
else if (error.message) {
|
|
2079
|
+
userMessage = `Lỗi kết nối: ${error.message}`;
|
|
2080
|
+
}
|
|
2081
|
+
else if (error.error?.message) {
|
|
2082
|
+
userMessage = error.error.message;
|
|
2083
|
+
}
|
|
2084
|
+
observer.error(this.buildHttpError(userMessage, {
|
|
2085
|
+
status: error?.status,
|
|
2086
|
+
statusText: error?.statusText,
|
|
2087
|
+
url: error?.url,
|
|
2088
|
+
cause: error,
|
|
2089
|
+
}));
|
|
2090
|
+
});
|
|
2091
|
+
return () => {
|
|
2092
|
+
if (xhr) {
|
|
2093
|
+
xhr.abort();
|
|
2094
|
+
xhr = null;
|
|
2095
|
+
}
|
|
2096
|
+
};
|
|
2097
|
+
});
|
|
2098
|
+
}
|
|
2099
|
+
/**
|
|
2100
|
+
* Tạo tên file unique bằng cách thêm timestamp và random string
|
|
2101
|
+
* Format: originalname-timestamp-random.ext
|
|
2102
|
+
*/
|
|
2103
|
+
generateUniqueFileName(originalFileName) {
|
|
2104
|
+
const timestamp = Date.now();
|
|
2105
|
+
const randomString = Math.random().toString(36).substring(2, 9);
|
|
2106
|
+
// Tách tên file và extension
|
|
2107
|
+
const lastDotIndex = originalFileName.lastIndexOf(".");
|
|
2108
|
+
if (lastDotIndex === -1) {
|
|
2109
|
+
// Không có extension
|
|
2110
|
+
return `${originalFileName}-${timestamp}-${randomString}`;
|
|
2111
|
+
}
|
|
2112
|
+
const nameWithoutExt = originalFileName.substring(0, lastDotIndex);
|
|
2113
|
+
const extension = originalFileName.substring(lastDotIndex);
|
|
2114
|
+
return `${nameWithoutExt}-${timestamp}-${randomString}${extension}`;
|
|
2115
|
+
}
|
|
2116
|
+
extractFileName(filePath) {
|
|
2117
|
+
return filePath.split("/").pop() || "";
|
|
2118
|
+
}
|
|
2119
|
+
buildResult(file, filePath, url) {
|
|
2120
|
+
return {
|
|
2121
|
+
url,
|
|
2122
|
+
filePath,
|
|
2123
|
+
fileName: this.extractFileName(filePath) || file.name,
|
|
2124
|
+
originalName: file.name,
|
|
2125
|
+
};
|
|
2126
|
+
}
|
|
1743
2127
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: FileAIA_GCSService, deps: [{ token: i1$2.HttpClient }, { token: ApiService }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1744
2128
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: FileAIA_GCSService, providedIn: "root" }); }
|
|
1745
2129
|
}
|
|
@@ -1754,6 +2138,22 @@ class FileWAPGCSService {
|
|
|
1754
2138
|
this._apiService = _apiService;
|
|
1755
2139
|
this.baseUrl = "/api1/files/wap-gcs";
|
|
1756
2140
|
}
|
|
2141
|
+
/**
|
|
2142
|
+
* Build an Error object but keep HTTP-related metadata for callers that need to branch by status.
|
|
2143
|
+
* Note: We intentionally do NOT change business flow; we only enrich the error instance.
|
|
2144
|
+
*/
|
|
2145
|
+
buildHttpError(message, meta) {
|
|
2146
|
+
const err = new Error(message);
|
|
2147
|
+
if (meta?.status !== undefined)
|
|
2148
|
+
err.status = meta.status;
|
|
2149
|
+
if (meta?.statusText !== undefined)
|
|
2150
|
+
err.statusText = meta.statusText;
|
|
2151
|
+
if (meta?.url !== undefined)
|
|
2152
|
+
err.url = meta.url;
|
|
2153
|
+
if (meta?.cause !== undefined)
|
|
2154
|
+
err.cause = meta.cause;
|
|
2155
|
+
return err;
|
|
2156
|
+
}
|
|
1757
2157
|
getSignedLink(filePath) {
|
|
1758
2158
|
return this._apiService.post(`${this.baseUrl}/getSignedLink?filePath=${filePath}`);
|
|
1759
2159
|
}
|
|
@@ -1768,7 +2168,7 @@ class FileWAPGCSService {
|
|
|
1768
2168
|
formData.append("file", file);
|
|
1769
2169
|
const req = new HttpRequest("POST", `${this.baseUrl}/attachFile`, formData, {
|
|
1770
2170
|
reportProgress: true,
|
|
1771
|
-
responseType: "json"
|
|
2171
|
+
responseType: "json",
|
|
1772
2172
|
});
|
|
1773
2173
|
return this._http.request(req);
|
|
1774
2174
|
}
|
|
@@ -1777,17 +2177,17 @@ class FileWAPGCSService {
|
|
|
1777
2177
|
formData.append("file", file);
|
|
1778
2178
|
const req = new HttpRequest("POST", `${this.baseUrl}/attachFile`, formData, {
|
|
1779
2179
|
reportProgress: true,
|
|
1780
|
-
responseType: "json"
|
|
2180
|
+
responseType: "json",
|
|
1781
2181
|
});
|
|
1782
2182
|
return firstValueFrom(this._http.request(req));
|
|
1783
2183
|
}
|
|
1784
2184
|
uploadFile(file) {
|
|
1785
|
-
return new Observable(observer => {
|
|
2185
|
+
return new Observable((observer) => {
|
|
1786
2186
|
let xhr = null;
|
|
1787
2187
|
// Tạo tên file unique để tránh trùng tên khi upload nhiều lần
|
|
1788
2188
|
const uniqueFileName = this.generateUniqueFileName(file.name);
|
|
1789
2189
|
firstValueFrom(this.getPresignedLink(uniqueFileName))
|
|
1790
|
-
.then(presignedData => {
|
|
2190
|
+
.then((presignedData) => {
|
|
1791
2191
|
if (!presignedData) {
|
|
1792
2192
|
throw new Error("Không nhận được presigned link từ server");
|
|
1793
2193
|
}
|
|
@@ -1799,7 +2199,10 @@ class FileWAPGCSService {
|
|
|
1799
2199
|
if (!presignedURL) {
|
|
1800
2200
|
throw new Error("Presigned URL không hợp lệ");
|
|
1801
2201
|
}
|
|
1802
|
-
const contentType =
|
|
2202
|
+
const contentType = presignedData.ContentType ||
|
|
2203
|
+
presignedData.contentType ||
|
|
2204
|
+
file.type ||
|
|
2205
|
+
"application/octet-stream";
|
|
1803
2206
|
xhr = new XMLHttpRequest();
|
|
1804
2207
|
const cleanup = () => {
|
|
1805
2208
|
if (xhr) {
|
|
@@ -1833,13 +2236,22 @@ class FileWAPGCSService {
|
|
|
1833
2236
|
cleanup();
|
|
1834
2237
|
const msg = err?.message ||
|
|
1835
2238
|
"Upload thành công nhưng setPublic thất bại. Vui lòng thử lại.";
|
|
1836
|
-
observer.error(
|
|
2239
|
+
observer.error(this.buildHttpError(msg, {
|
|
2240
|
+
status: xhr?.status,
|
|
2241
|
+
statusText: xhr?.statusText,
|
|
2242
|
+
url: presignedURL,
|
|
2243
|
+
cause: err,
|
|
2244
|
+
}));
|
|
1837
2245
|
}
|
|
1838
2246
|
}
|
|
1839
2247
|
else if (xhr) {
|
|
1840
2248
|
const errorMessage = `Upload file thất bại: ${xhr.statusText} (${xhr.status})`;
|
|
1841
2249
|
cleanup();
|
|
1842
|
-
observer.error(
|
|
2250
|
+
observer.error(this.buildHttpError(errorMessage, {
|
|
2251
|
+
status: xhr.status,
|
|
2252
|
+
statusText: xhr.statusText,
|
|
2253
|
+
url: presignedURL,
|
|
2254
|
+
}));
|
|
1843
2255
|
}
|
|
1844
2256
|
};
|
|
1845
2257
|
const errorHandler = () => {
|
|
@@ -1857,11 +2269,19 @@ class FileWAPGCSService {
|
|
|
1857
2269
|
userMessage = `Lỗi yêu cầu: ${xhr.statusText}`;
|
|
1858
2270
|
}
|
|
1859
2271
|
cleanup();
|
|
1860
|
-
observer.error(
|
|
2272
|
+
observer.error(this.buildHttpError(userMessage, {
|
|
2273
|
+
status: xhr.status,
|
|
2274
|
+
statusText: xhr.statusText,
|
|
2275
|
+
url: presignedURL,
|
|
2276
|
+
}));
|
|
1861
2277
|
};
|
|
1862
2278
|
const timeoutHandler = () => {
|
|
1863
2279
|
cleanup();
|
|
1864
|
-
observer.error(
|
|
2280
|
+
observer.error(this.buildHttpError("Kết nối đến máy chủ hết thời gian. Vui lòng thử lại.", {
|
|
2281
|
+
status: xhr?.status,
|
|
2282
|
+
statusText: xhr?.statusText,
|
|
2283
|
+
url: presignedURL,
|
|
2284
|
+
}));
|
|
1865
2285
|
};
|
|
1866
2286
|
xhr.upload.addEventListener("progress", progressHandler);
|
|
1867
2287
|
xhr.addEventListener("load", loadHandler);
|
|
@@ -1872,7 +2292,7 @@ class FileWAPGCSService {
|
|
|
1872
2292
|
xhr.setRequestHeader("Content-Type", contentType);
|
|
1873
2293
|
xhr.send(file);
|
|
1874
2294
|
})
|
|
1875
|
-
.catch(error => {
|
|
2295
|
+
.catch((error) => {
|
|
1876
2296
|
if (xhr) {
|
|
1877
2297
|
xhr.abort();
|
|
1878
2298
|
xhr = null;
|
|
@@ -1897,7 +2317,13 @@ class FileWAPGCSService {
|
|
|
1897
2317
|
else if (error.error?.message) {
|
|
1898
2318
|
userMessage = error.error.message;
|
|
1899
2319
|
}
|
|
1900
|
-
observer.error(
|
|
2320
|
+
observer.error(this.buildHttpError(userMessage, {
|
|
2321
|
+
// In this catch block, the error is often HttpErrorResponse from getPresignedLink().
|
|
2322
|
+
status: error?.status,
|
|
2323
|
+
statusText: error?.statusText,
|
|
2324
|
+
url: error?.url,
|
|
2325
|
+
cause: error,
|
|
2326
|
+
}));
|
|
1901
2327
|
});
|
|
1902
2328
|
return () => {
|
|
1903
2329
|
if (xhr) {
|
|
@@ -1932,7 +2358,7 @@ class FileWAPGCSService {
|
|
|
1932
2358
|
url,
|
|
1933
2359
|
filePath,
|
|
1934
2360
|
fileName: this.extractFileName(filePath) || file.name,
|
|
1935
|
-
originalName: file.name
|
|
2361
|
+
originalName: file.name,
|
|
1936
2362
|
};
|
|
1937
2363
|
}
|
|
1938
2364
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: FileWAPGCSService, deps: [{ token: i1$2.HttpClient }, { token: ApiService }], target: i0.ɵɵFactoryTarget.Injectable }); }
|