@progalaxyelabs/ngx-stonescriptphp-client 1.5.0 → 1.6.0
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.
|
@@ -196,6 +196,12 @@ class MyEnvironmentModel {
|
|
|
196
196
|
};
|
|
197
197
|
apiServer = { host: '' };
|
|
198
198
|
chatServer = { host: '' };
|
|
199
|
+
/**
|
|
200
|
+
* Files service server configuration
|
|
201
|
+
* Used by FilesService for file upload/download operations
|
|
202
|
+
* @example { host: 'https://files.progalaxyelabs.com/api/' }
|
|
203
|
+
*/
|
|
204
|
+
filesServer;
|
|
199
205
|
/**
|
|
200
206
|
* Authentication configuration
|
|
201
207
|
* @default { mode: 'cookie', refreshEndpoint: '/auth/refresh', useCsrf: true }
|
|
@@ -1492,6 +1498,270 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
|
|
|
1492
1498
|
}]
|
|
1493
1499
|
}], ctorParameters: () => [] });
|
|
1494
1500
|
|
|
1501
|
+
/**
|
|
1502
|
+
* Service for interacting with the stonescriptphp-files server.
|
|
1503
|
+
* Handles file upload, download, list, and delete operations
|
|
1504
|
+
* with automatic Bearer token injection and 401 refresh handling.
|
|
1505
|
+
*/
|
|
1506
|
+
class FilesService {
|
|
1507
|
+
tokens;
|
|
1508
|
+
signinStatus;
|
|
1509
|
+
environment;
|
|
1510
|
+
csrf;
|
|
1511
|
+
host = '';
|
|
1512
|
+
apiHost = '';
|
|
1513
|
+
authConfig;
|
|
1514
|
+
constructor(tokens, signinStatus, environment, csrf) {
|
|
1515
|
+
this.tokens = tokens;
|
|
1516
|
+
this.signinStatus = signinStatus;
|
|
1517
|
+
this.environment = environment;
|
|
1518
|
+
this.csrf = csrf;
|
|
1519
|
+
this.host = environment.filesServer?.host || '';
|
|
1520
|
+
this.apiHost = environment.apiServer.host;
|
|
1521
|
+
this.authConfig = {
|
|
1522
|
+
mode: environment.auth?.mode || 'cookie',
|
|
1523
|
+
refreshEndpoint: environment.auth?.refreshEndpoint,
|
|
1524
|
+
useCsrf: environment.auth?.useCsrf,
|
|
1525
|
+
refreshTokenCookieName: environment.auth?.refreshTokenCookieName || 'refresh_token',
|
|
1526
|
+
csrfTokenCookieName: environment.auth?.csrfTokenCookieName || 'csrf_token',
|
|
1527
|
+
csrfHeaderName: environment.auth?.csrfHeaderName || 'X-CSRF-Token'
|
|
1528
|
+
};
|
|
1529
|
+
if (!this.authConfig.refreshEndpoint) {
|
|
1530
|
+
this.authConfig.refreshEndpoint = this.authConfig.mode === 'cookie'
|
|
1531
|
+
? '/auth/refresh'
|
|
1532
|
+
: '/user/refresh_access';
|
|
1533
|
+
}
|
|
1534
|
+
if (this.authConfig.useCsrf === undefined) {
|
|
1535
|
+
this.authConfig.useCsrf = this.authConfig.mode === 'cookie';
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
/**
|
|
1539
|
+
* Check if the files server is configured.
|
|
1540
|
+
*/
|
|
1541
|
+
isConfigured() {
|
|
1542
|
+
return !!this.host;
|
|
1543
|
+
}
|
|
1544
|
+
/**
|
|
1545
|
+
* Upload a file to the files service.
|
|
1546
|
+
* Uses FormData — does NOT set Content-Type header (browser sets multipart boundary).
|
|
1547
|
+
*
|
|
1548
|
+
* @param file The File object to upload
|
|
1549
|
+
* @param entityType Optional entity type for server-side reference linking
|
|
1550
|
+
* @param entityId Optional entity ID for server-side reference linking
|
|
1551
|
+
* @returns Promise resolving to the upload result
|
|
1552
|
+
*/
|
|
1553
|
+
async upload(file, entityType, entityId) {
|
|
1554
|
+
const formData = new FormData();
|
|
1555
|
+
formData.append('file', file);
|
|
1556
|
+
if (entityType) {
|
|
1557
|
+
formData.append('entityType', entityType);
|
|
1558
|
+
}
|
|
1559
|
+
if (entityId) {
|
|
1560
|
+
formData.append('entityId', entityId);
|
|
1561
|
+
}
|
|
1562
|
+
const url = this.host + 'upload';
|
|
1563
|
+
const options = {
|
|
1564
|
+
method: 'POST',
|
|
1565
|
+
mode: 'cors',
|
|
1566
|
+
redirect: 'error',
|
|
1567
|
+
body: formData
|
|
1568
|
+
// No Content-Type header — browser sets multipart boundary automatically
|
|
1569
|
+
};
|
|
1570
|
+
const response = await this.requestWithRetry(url, options);
|
|
1571
|
+
return response.file;
|
|
1572
|
+
}
|
|
1573
|
+
/**
|
|
1574
|
+
* Download a file from the files service.
|
|
1575
|
+
* Returns a Blob suitable for URL.createObjectURL().
|
|
1576
|
+
*
|
|
1577
|
+
* @param fileId UUID of the file to download
|
|
1578
|
+
* @returns Promise resolving to the file Blob
|
|
1579
|
+
*/
|
|
1580
|
+
async download(fileId) {
|
|
1581
|
+
const url = this.host + 'files/' + fileId;
|
|
1582
|
+
const options = {
|
|
1583
|
+
method: 'GET',
|
|
1584
|
+
mode: 'cors',
|
|
1585
|
+
redirect: 'error'
|
|
1586
|
+
};
|
|
1587
|
+
this.includeAccessToken(options);
|
|
1588
|
+
let response = await fetch(url, options);
|
|
1589
|
+
if (response.status === 401) {
|
|
1590
|
+
const refreshed = await this.refreshAccessToken();
|
|
1591
|
+
if (refreshed) {
|
|
1592
|
+
this.includeAccessToken(options);
|
|
1593
|
+
response = await fetch(url, options);
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
if (response.status === 401) {
|
|
1597
|
+
this.signinStatus.signedOut();
|
|
1598
|
+
}
|
|
1599
|
+
if (!response.ok) {
|
|
1600
|
+
throw new Error(`Download failed: ${response.status} ${response.statusText}`);
|
|
1601
|
+
}
|
|
1602
|
+
return response.blob();
|
|
1603
|
+
}
|
|
1604
|
+
/**
|
|
1605
|
+
* List all files for the current user.
|
|
1606
|
+
*
|
|
1607
|
+
* @returns Promise resolving to array of file metadata
|
|
1608
|
+
*/
|
|
1609
|
+
async list() {
|
|
1610
|
+
const url = this.host + 'files';
|
|
1611
|
+
const options = {
|
|
1612
|
+
method: 'GET',
|
|
1613
|
+
mode: 'cors',
|
|
1614
|
+
redirect: 'error'
|
|
1615
|
+
};
|
|
1616
|
+
const response = await this.requestWithRetry(url, options);
|
|
1617
|
+
return response.files;
|
|
1618
|
+
}
|
|
1619
|
+
/**
|
|
1620
|
+
* Delete a file from the files service.
|
|
1621
|
+
*
|
|
1622
|
+
* @param fileId UUID of the file to delete
|
|
1623
|
+
* @returns Promise resolving to true on success
|
|
1624
|
+
*/
|
|
1625
|
+
async delete(fileId) {
|
|
1626
|
+
const url = this.host + 'files/' + fileId;
|
|
1627
|
+
const options = {
|
|
1628
|
+
method: 'DELETE',
|
|
1629
|
+
mode: 'cors',
|
|
1630
|
+
redirect: 'error'
|
|
1631
|
+
};
|
|
1632
|
+
const response = await this.requestWithRetry(url, options);
|
|
1633
|
+
return response.success;
|
|
1634
|
+
}
|
|
1635
|
+
/**
|
|
1636
|
+
* Make a request with automatic Bearer token injection and 401 retry.
|
|
1637
|
+
*/
|
|
1638
|
+
async requestWithRetry(url, options) {
|
|
1639
|
+
this.includeAccessToken(options);
|
|
1640
|
+
let response = await fetch(url, options);
|
|
1641
|
+
if (response.status === 401) {
|
|
1642
|
+
const refreshed = await this.refreshAccessToken();
|
|
1643
|
+
if (refreshed) {
|
|
1644
|
+
this.includeAccessToken(options);
|
|
1645
|
+
response = await fetch(url, options);
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
if (response.status === 401) {
|
|
1649
|
+
this.signinStatus.signedOut();
|
|
1650
|
+
}
|
|
1651
|
+
if (!response.ok) {
|
|
1652
|
+
const body = await response.json().catch(() => ({}));
|
|
1653
|
+
throw new Error(body.message || `Request failed: ${response.status}`);
|
|
1654
|
+
}
|
|
1655
|
+
return response.json();
|
|
1656
|
+
}
|
|
1657
|
+
includeAccessToken(options) {
|
|
1658
|
+
const accessToken = this.tokens.getAccessToken();
|
|
1659
|
+
if (!accessToken)
|
|
1660
|
+
return;
|
|
1661
|
+
if (!options.headers) {
|
|
1662
|
+
options.headers = {};
|
|
1663
|
+
}
|
|
1664
|
+
options.headers['Authorization'] = 'Bearer ' + accessToken;
|
|
1665
|
+
}
|
|
1666
|
+
async refreshAccessToken() {
|
|
1667
|
+
if (this.authConfig.mode === 'none') {
|
|
1668
|
+
return false;
|
|
1669
|
+
}
|
|
1670
|
+
if (this.authConfig.mode === 'cookie') {
|
|
1671
|
+
return this.refreshAccessTokenCookieMode();
|
|
1672
|
+
}
|
|
1673
|
+
else {
|
|
1674
|
+
return this.refreshAccessTokenBodyMode();
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
async refreshAccessTokenCookieMode() {
|
|
1678
|
+
try {
|
|
1679
|
+
const refreshTokenUrl = this.apiHost + this.authConfig.refreshEndpoint.replace(/^\/+/, '');
|
|
1680
|
+
const headers = {
|
|
1681
|
+
'Content-Type': 'application/json'
|
|
1682
|
+
};
|
|
1683
|
+
if (this.authConfig.useCsrf) {
|
|
1684
|
+
const csrfToken = this.csrf.getCsrfToken(this.authConfig.csrfTokenCookieName);
|
|
1685
|
+
if (!csrfToken) {
|
|
1686
|
+
return false;
|
|
1687
|
+
}
|
|
1688
|
+
headers[this.authConfig.csrfHeaderName] = csrfToken;
|
|
1689
|
+
}
|
|
1690
|
+
const response = await fetch(refreshTokenUrl, {
|
|
1691
|
+
method: 'POST',
|
|
1692
|
+
mode: 'cors',
|
|
1693
|
+
credentials: 'include',
|
|
1694
|
+
redirect: 'error',
|
|
1695
|
+
headers
|
|
1696
|
+
});
|
|
1697
|
+
if (!response.ok) {
|
|
1698
|
+
this.tokens.clear();
|
|
1699
|
+
return false;
|
|
1700
|
+
}
|
|
1701
|
+
const data = await response.json();
|
|
1702
|
+
if (!data || data.status !== 'ok') {
|
|
1703
|
+
return false;
|
|
1704
|
+
}
|
|
1705
|
+
const newAccessToken = data.data?.access_token || data.access_token;
|
|
1706
|
+
if (!newAccessToken) {
|
|
1707
|
+
return false;
|
|
1708
|
+
}
|
|
1709
|
+
this.tokens.setAccessToken(newAccessToken);
|
|
1710
|
+
return true;
|
|
1711
|
+
}
|
|
1712
|
+
catch {
|
|
1713
|
+
this.tokens.clear();
|
|
1714
|
+
return false;
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
async refreshAccessTokenBodyMode() {
|
|
1718
|
+
try {
|
|
1719
|
+
const refreshToken = this.tokens.getRefreshToken();
|
|
1720
|
+
if (!refreshToken) {
|
|
1721
|
+
return false;
|
|
1722
|
+
}
|
|
1723
|
+
const accessToken = this.tokens.getAccessToken();
|
|
1724
|
+
const refreshTokenUrl = this.apiHost + this.authConfig.refreshEndpoint.replace(/^\/+/, '');
|
|
1725
|
+
const response = await fetch(refreshTokenUrl, {
|
|
1726
|
+
method: 'POST',
|
|
1727
|
+
mode: 'cors',
|
|
1728
|
+
redirect: 'error',
|
|
1729
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1730
|
+
body: JSON.stringify({
|
|
1731
|
+
access_token: accessToken,
|
|
1732
|
+
refresh_token: refreshToken
|
|
1733
|
+
})
|
|
1734
|
+
});
|
|
1735
|
+
if (!response.ok) {
|
|
1736
|
+
this.tokens.clear();
|
|
1737
|
+
return false;
|
|
1738
|
+
}
|
|
1739
|
+
const data = await response.json();
|
|
1740
|
+
if (!data) {
|
|
1741
|
+
return false;
|
|
1742
|
+
}
|
|
1743
|
+
const newAccessToken = data.data?.access_token || data.access_token;
|
|
1744
|
+
if (!newAccessToken) {
|
|
1745
|
+
return false;
|
|
1746
|
+
}
|
|
1747
|
+
this.tokens.setTokens(newAccessToken, refreshToken);
|
|
1748
|
+
return true;
|
|
1749
|
+
}
|
|
1750
|
+
catch {
|
|
1751
|
+
this.tokens.clear();
|
|
1752
|
+
return false;
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: FilesService, deps: [{ token: TokenService }, { token: SigninStatusService }, { token: MyEnvironmentModel }, { token: CsrfService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1756
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: FilesService, providedIn: 'root' });
|
|
1757
|
+
}
|
|
1758
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: FilesService, decorators: [{
|
|
1759
|
+
type: Injectable,
|
|
1760
|
+
args: [{
|
|
1761
|
+
providedIn: 'root'
|
|
1762
|
+
}]
|
|
1763
|
+
}], ctorParameters: () => [{ type: TokenService }, { type: SigninStatusService }, { type: MyEnvironmentModel }, { type: CsrfService }] });
|
|
1764
|
+
|
|
1495
1765
|
class NgxStoneScriptPhpClientModule {
|
|
1496
1766
|
static forRoot(environment) {
|
|
1497
1767
|
return {
|
|
@@ -3779,5 +4049,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
|
|
|
3779
4049
|
* Generated bundle index. Do not edit.
|
|
3780
4050
|
*/
|
|
3781
4051
|
|
|
3782
|
-
export { ApiConnectionService, ApiResponse, AuthPageComponent, AuthService, CsrfService, DbService, LoginDialogComponent, MyEnvironmentModel, NgxStoneScriptPhpClientModule, RegisterComponent, SigninStatusService, TenantLoginComponent, TenantLoginDialogComponent, TenantRegisterComponent, TenantRegisterDialogComponent, TokenService, VerifyStatus };
|
|
4052
|
+
export { ApiConnectionService, ApiResponse, AuthPageComponent, AuthService, CsrfService, DbService, FilesService, LoginDialogComponent, MyEnvironmentModel, NgxStoneScriptPhpClientModule, RegisterComponent, SigninStatusService, TenantLoginComponent, TenantLoginDialogComponent, TenantRegisterComponent, TenantRegisterDialogComponent, TokenService, VerifyStatus };
|
|
3783
4053
|
//# sourceMappingURL=progalaxyelabs-ngx-stonescriptphp-client.mjs.map
|