@singularlogic/coreplatts 0.0.5 → 0.0.7

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/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # @singularlogic/coreplatts
2
2
 
3
- A TypeScript client library to access the **BUILDSPACE** identity and storage APIs.
3
+ A TypeScript client library to access the **BUILDSPACE** identity and storage APIs.
4
+
4
5
  Built to work with [Keycloak](https://www.keycloak.org/) and [MinIO](https://min.io/), this library wraps REST endpoints and handles authentication, token refresh, organization and folder management.
5
6
 
6
7
  ---
@@ -35,19 +36,34 @@ const token = await client.login('user@example.com', 'password');
35
36
  console.log('Access token:', token.access_token);
36
37
  ```
37
38
 
38
- Tokens are stored in `localStorage` or `sessionStorage` and automatically refreshed when expired.
39
+ Tokens are stored in `sessionStorage` and are **automatically refreshed when expired**.
40
+
41
+ ### ⚠️ For Web Applications: Use an OIDC or Keycloak Client
42
+
43
+ We **strongly recommend** using an **OpenID Connect (OIDC)** or **Keycloak client** for web applications.
44
+
45
+ **Why?**
46
+
47
+ - ✅ More secure: handles redirect flows, token validation, and silent refresh securely.
48
+ - 🔄 Seamless session management: avoids manual token handling and refresh logic.
49
+ - 🔐 Built-in CSRF and token expiration protection.
50
+
51
+ #### Great frontend OIDC libraries:
52
+
53
+ - [oidc-client-ts](https://www.npmjs.com/package/oidc-client-ts)
54
+ - [keycloak-js](https://www.npmjs.com/package/keycloak-js)
55
+
56
+ > The `client.login(email, password)` method is designed for **CLI tools, backend services, development, and test automation** — not for production browser-based apps.
39
57
 
40
58
  ---
41
59
 
42
60
  ## 👤 User Management
43
61
 
44
62
  ```ts
45
- const me = await client.myUser(); // auto-includes access token
63
+ const me = await client.myUser();
46
64
  console.log(me.username);
47
65
  ```
48
66
 
49
- Register new users:
50
-
51
67
  ```ts
52
68
  await client.register(
53
69
  'new@user.com',
@@ -81,71 +97,30 @@ await client.addToOrganization('MyNewOrg', {
81
97
  ## 📂 Folder & File Management
82
98
 
83
99
  ```ts
84
- const folder = await client.getFolderByName('project-data');
85
- console.log(folder.files);
86
- ```
87
100
 
88
- You can also upload and download files using `Folder` and `File` methods.
101
+ const folder = await client.getFolderByName('project-data');
89
102
 
90
- Example:
103
+ const folderItems = await folder.listItems();
91
104
 
92
- ```ts
93
105
  const file = await folder.getFileByName('report.pdf');
94
106
  await file.download();
95
107
  ```
96
108
 
97
- ---
98
-
99
- ## 🔁 Automatic Token Handling
100
-
101
- All token-sensitive methods use a utility that:
102
- - Checks if the access token is expired
103
- - Refreshes it using `refresh_token` if needed
104
- - Uses `localStorage` or `sessionStorage` automatically
105
-
106
- ---
107
-
108
- ## 🧪 Example in Angular Service
109
+ ### Upload a File
109
110
 
110
111
  ```ts
111
- import { Injectable } from '@angular/core';
112
- import { Client } from '@singularlogic/coreplatts';
113
- import { environment } from '../environments/environment';
114
-
115
- @Injectable({ providedIn: 'root' })
116
- export class AuthService {
117
- client = new Client(environment.managementURL, environment.accountURL);
118
-
119
- getCurrentUser() {
120
- return this.client.myUser();
121
- }
122
- }
112
+ await folder.uploadFile(
113
+ myFile,
114
+ { title: 'dataset.csv' },
115
+ 5 * 1024 * 1024, // chunk size
116
+ 10, // concurrency
117
+ 3600 // timeout
118
+ );
123
119
  ```
124
120
 
125
121
  ---
126
122
 
127
- ## 🧱 Core Classes
128
-
129
- ### `Client`
130
-
131
- - Handles auth, session, and proxies to:
132
- - `UserConsumer`
133
- - `GroupConsumer`
134
- - `BucketConsumer`
135
- - `FolderConsumer`
136
-
137
- ### `Folder`
138
-
139
- - Can fetch files
140
- - Can upload files with chunking and concurrency control
141
-
142
- ### `File`
143
-
144
- - Can be downloaded by ID or name
145
-
146
- ---
147
-
148
- ## 📘 API Summary
123
+ ### 📘 Main Client
149
124
 
150
125
  | Method | Description |
151
126
  |--------|-------------|
@@ -161,6 +136,34 @@ export class AuthService {
161
136
 
162
137
  ---
163
138
 
139
+ ### 📘 Folder Class
140
+
141
+ | Method | Description |
142
+ |--------|-------------|
143
+ | `listItems()` | List folder contents |
144
+ | `getFileByID(id)` | Retrieve file by ID |
145
+ | `getFileByName(name)` | Retrieve file by name |
146
+ | `uploadFile(...)` | Upload file with chunked multipart support |
147
+
148
+ ---
149
+
150
+ ### 📘 File Class
151
+
152
+ | Method | Description |
153
+ |--------|-------------|
154
+ | `download()` | Download file with chunked concurrency |
155
+ | `getFilename()` | Get complete file name from metadata |
156
+
157
+ ---
158
+
159
+ ## 🔁 Automatic Token Handling
160
+
161
+ All token-sensitive methods use a utility that:
162
+
163
+ - Checks token expiration
164
+ - Automatically refreshes access tokens
165
+ - Uses `sessionStorage` only
166
+
164
167
  ## 📄 License
165
168
 
166
169
  MIT © [SingularLogic S.A.](https://www.singularlogic.eu)
package/dist/index.d.ts CHANGED
@@ -155,7 +155,8 @@ declare class Client implements IClient$1 {
155
155
  private _bucketConsumer;
156
156
  private _folderConsumer;
157
157
  constructor(_managementURL: string, _accountURL: string);
158
- private _setSession;
158
+ setSession(session: OidcToken): void;
159
+ getSessionVariable(key: string): string | null;
159
160
  login(email: string, password: string): Promise<OidcToken>;
160
161
  myUser(token?: string): Promise<User$1>;
161
162
  allOrganizations(token?: string): Promise<Group$1[]>;
package/dist/index.js CHANGED
@@ -272,20 +272,40 @@ var BucketConsumer = class extends BaseConsumer {
272
272
  function withValidToken(fn, accountsAPI) {
273
273
  return function(...args) {
274
274
  return __async(this, null, function* () {
275
- const isStorageAvailable = typeof localStorage !== "undefined";
276
275
  const now = Math.floor(Date.now() / 1e3);
277
- if (!isStorageAvailable) {
278
- console.warn("\u26A0\uFE0F localStorage not available. You must pass token manually.");
279
- return Promise.resolve(fn.apply(this, args));
276
+ const storage = typeof window !== "undefined" ? window.sessionStorage : globalThis.sessionStorage;
277
+ if (!storage) {
278
+ console.warn("\u26A0\uFE0F sessionStorage not available");
279
+ return fn.apply(this, args);
280
280
  }
281
- let token = localStorage.getItem("token");
282
- const refreshToken = localStorage.getItem("refresh_token");
283
- const exp = Number(localStorage.getItem("exp") || "0");
284
- const refreshExp = Number(localStorage.getItem("refresh_exp") || "0");
285
- if (!token || now >= exp) {
286
- if (!refreshToken || now >= refreshExp) {
287
- console.warn("\u26A0\uFE0F Session expired. Please log in again.");
288
- return Promise.resolve(fn.apply(this, args));
281
+ let token = storage.getItem("access_token");
282
+ let refreshToken = storage.getItem("refresh_token");
283
+ let exp = 0;
284
+ let refreshExp = 0;
285
+ const claimsRaw = storage.getItem("id_token_claims_obj");
286
+ if (claimsRaw) {
287
+ try {
288
+ const claims = JSON.parse(claimsRaw);
289
+ if (claims.exp) exp = claims.exp;
290
+ if (claims.iat && claims.exp) {
291
+ const duration = claims.exp - claims.iat;
292
+ refreshExp = claims.iat + 3 * duration;
293
+ }
294
+ } catch (e) {
295
+ console.warn("\u26A0\uFE0F Could not parse id_token_claims_obj:", e);
296
+ }
297
+ } else {
298
+ exp = Number(storage.getItem("refresh_expires_in") || "0");
299
+ refreshExp = Number(storage.getItem("refresh_exp") || "0");
300
+ }
301
+ if (!token || !refreshToken) {
302
+ console.warn("\u26A0\uFE0F No tokens found in sessionStorage.");
303
+ return fn.apply(this, args);
304
+ }
305
+ if (now >= exp) {
306
+ if (now >= refreshExp) {
307
+ console.warn("\u26A0\uFE0F Both access and refresh tokens expired.");
308
+ return fn.apply(this, args);
289
309
  }
290
310
  try {
291
311
  const response = yield fetch(`${accountsAPI}/user/refresh`, {
@@ -295,20 +315,20 @@ function withValidToken(fn, accountsAPI) {
295
315
  });
296
316
  if (!response.ok) throw new Error("Token refresh failed");
297
317
  const session = yield response.json();
318
+ token = session.access_token;
319
+ refreshToken = session.refresh_token;
298
320
  const newExp = now + session.expires_in;
299
321
  const newRefreshExp = now + session.refresh_expires_in;
300
- localStorage.setItem("token", session.access_token);
301
- localStorage.setItem("refresh_token", session.refresh_token);
302
- localStorage.setItem("exp", String(newExp));
303
- localStorage.setItem("refresh_exp", String(newRefreshExp));
304
- token = session.access_token;
305
- } catch (e) {
306
- console.error("\u{1F510} Token refresh failed:", e);
322
+ storage.setItem("access_token", token);
323
+ storage.setItem("refresh_token", refreshToken);
324
+ storage.setItem("refresh_expires_in", String(newExp));
325
+ storage.setItem("refresh_exp", String(newRefreshExp));
326
+ } catch (err) {
327
+ console.error("\u{1F510} Token refresh failed:", err);
307
328
  throw new Error("Could not refresh session.");
308
329
  }
309
330
  }
310
- const result = fn.apply(this, [...args, token]);
311
- return result instanceof Promise ? result : Promise.resolve(result);
331
+ return yield fn.apply(this, [...args, token]);
312
332
  });
313
333
  };
314
334
  }
@@ -787,36 +807,37 @@ var Client = class {
787
807
  this.getFolderByName = withValidToken(this.getFolderByName.bind(this), _accountURL);
788
808
  this.getFolderByID = withValidToken(this.getFolderByID.bind(this), _accountURL);
789
809
  }
790
- // public setSession(session: OidcToken): void {
791
- // const storage = typeof window !== "undefined" ? sessionStorage : global.sessionStorage;
792
- // if (storage) {
793
- // storage.setItem('token', session.access_token);
794
- // storage.setItem('refresh_token', session.refresh_token);
795
- // storage.setItem('exp', String(session.expires_in));
796
- // storage.setItem('refresh_exp', String(session.refresh_expires_in));
797
- // } else {
798
- // console.warn("Session storage is not available.");
799
- // }
800
- // }
801
- // public getSessionVariable(key: string): string | null {
802
- // const storage = typeof window !== "undefined" ? sessionStorage : global.sessionStorage;
803
- // return storage ? storage.getItem(key) : null;
804
- // }
805
- _setSession(session) {
810
+ setSession(session) {
811
+ const storage = typeof window !== "undefined" ? sessionStorage : global.sessionStorage;
806
812
  const now = Math.floor(Date.now() / 1e3);
807
- if (typeof localStorage !== "undefined") {
808
- localStorage.setItem("token", session.access_token);
809
- localStorage.setItem("refresh_token", session.refresh_token);
810
- localStorage.setItem("exp", String(now + session.expires_in));
811
- localStorage.setItem("refresh_exp", String(now + session.refresh_expires_in));
813
+ if (storage) {
814
+ storage.setItem("access_token", session.access_token);
815
+ storage.setItem("refresh_token", session.refresh_token);
816
+ storage.setItem("refresh_expires_in", String(now + session.expires_in));
817
+ storage.setItem("refresh_exp", String(now + session.refresh_expires_in));
812
818
  } else {
813
- console.warn("\u26A0\uFE0F Local Storage not available. You will need to pass tokens manually.");
819
+ console.warn("Session storage is not available.");
814
820
  }
815
821
  }
822
+ getSessionVariable(key) {
823
+ const storage = typeof window !== "undefined" ? sessionStorage : global.sessionStorage;
824
+ return storage ? storage.getItem(key) : null;
825
+ }
826
+ // private _setSession(session: OidcToken): void {
827
+ // const now = Math.floor(Date.now() / 1000);
828
+ // if (typeof localStorage !== "undefined") {
829
+ // localStorage.setItem('token', session.access_token);
830
+ // localStorage.setItem('refresh_token', session.refresh_token);
831
+ // localStorage.setItem('exp', String(now + session.expires_in));
832
+ // localStorage.setItem('refresh_exp', String(now + session.refresh_expires_in));
833
+ // } else {
834
+ // console.warn("⚠️ Local Storage not available. You will need to pass tokens manually.");
835
+ // }
836
+ // }
816
837
  login(email, password) {
817
838
  return this._userConsumer.login(email, password).then(
818
839
  (resp) => {
819
- this._setSession(resp);
840
+ this.setSession(resp);
820
841
  return resp;
821
842
  }
822
843
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@singularlogic/coreplatts",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "files": [