@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