@haven-team/helix-sdk 1.0.12 → 1.0.13

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.
Files changed (2) hide show
  1. package/helix-sdk.js +62 -0
  2. package/package.json +1 -1
package/helix-sdk.js CHANGED
@@ -37,6 +37,16 @@
37
37
  * localhost unless `force: true`. Failures never break the app; the SDK
38
38
  * logs a warning and `enableLogRocket()` resolves to null. See
39
39
  * docs/logrocket.md for the full manual.
40
+ *
41
+ * Store scoping (which stores the user may access):
42
+ * const scope = await helix.getStoreScope();
43
+ * // { unrestricted: false, store_ids: [3, 7], source: 'grant' }
44
+ * if (await helix.canAccessStore(storeId)) { ... }
45
+ *
46
+ * Resolved from GET /api/v1/auth/authenticate (cached). Admins / district
47
+ * managers come back `unrestricted: true`; everyone else is limited to
48
+ * `store_ids`. Fails CLOSED on error. This shapes the UI; the API still
49
+ * enforces scope server-side. See docs/store-scoping.md.
40
50
  */
41
51
  (function (root, factory) {
42
52
  if (typeof module !== 'undefined' && module.exports) {
@@ -52,6 +62,19 @@
52
62
  const DEFAULT_LOGROCKET_APP_ID = 'lto7os/helix-frontend';
53
63
  const LOGROCKET_CDN = 'https://cdn.lr-ingest.com/LogRocket.min.js';
54
64
 
65
+ // Coerce whatever the identity endpoint returns into a well-formed store
66
+ // scope. Missing/garbage input fails CLOSED (no access) rather than open.
67
+ function normalizeStoreScope(s) {
68
+ if (!s || typeof s !== 'object') return { unrestricted: false, store_ids: [], source: 'error' };
69
+ return {
70
+ unrestricted: s.unrestricted === true,
71
+ store_ids: Array.isArray(s.store_ids)
72
+ ? s.store_ids.filter((v) => v != null && v !== '' && Number.isFinite(Number(v))).map(Number)
73
+ : [],
74
+ source: typeof s.source === 'string' ? s.source : 'unknown'
75
+ };
76
+ }
77
+
55
78
  class HelixSDK {
56
79
  constructor(options = {}) {
57
80
  this._ready = false;
@@ -66,6 +89,8 @@
66
89
  this._logrocket = null;
67
90
  this._lrShouldRecord = true;
68
91
  this._lrPromise = null;
92
+ this._storeScope = null;
93
+ this._storeScopePromise = null;
69
94
 
70
95
  if (options.logrocket) {
71
96
  this.enableLogRocket(options.logrocket === true ? {} : options.logrocket);
@@ -268,6 +293,43 @@
268
293
  return this._context?.token || null;
269
294
  }
270
295
 
296
+ /**
297
+ * Resolve the acting user's store scope — which stores they may access.
298
+ * Fetches GET /api/v1/auth/authenticate once and caches the result.
299
+ *
300
+ * Returns { unrestricted: boolean, store_ids: number[], source: string }.
301
+ * On any failure it returns a CLOSED default ({ unrestricted: false,
302
+ * store_ids: [], source: 'error' }) — fail closed, never accidentally
303
+ * grant access. Apps should still treat the server as the source of truth;
304
+ * this is for shaping the UI, not for security.
305
+ */
306
+ async getStoreScope() {
307
+ if (this._storeScope) return this._storeScope;
308
+ if (this._storeScopePromise) return this._storeScopePromise;
309
+ this._storeScopePromise = this.apiGet('/api/v1/auth/authenticate')
310
+ .then((res) => {
311
+ const s = res && res.user && res.user.store_scope;
312
+ this._storeScope = normalizeStoreScope(s);
313
+ return this._storeScope;
314
+ })
315
+ .catch((e) => {
316
+ console.warn('HelixSDK: getStoreScope failed, defaulting to no access', e);
317
+ this._storeScope = { unrestricted: false, store_ids: [], source: 'error' };
318
+ return this._storeScope;
319
+ });
320
+ return this._storeScopePromise;
321
+ }
322
+
323
+ /**
324
+ * True if the acting user may access the given store id. Resolves the
325
+ * store scope (cached) and checks it. Unrestricted users pass for any id.
326
+ */
327
+ async canAccessStore(storeId) {
328
+ const scope = await this.getStoreScope();
329
+ if (scope.unrestricted) return true;
330
+ return scope.store_ids.indexOf(Number(storeId)) !== -1;
331
+ }
332
+
271
333
  /** Make an authenticated GET request to the Helix API. */
272
334
  async apiGet(path) {
273
335
  return this._apiFetch('GET', path);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haven-team/helix-sdk",
3
- "version": "1.0.12",
3
+ "version": "1.0.13",
4
4
  "description": "Client-side SDK for Helix Apps embedded via iframe — auth context, API helpers, URL/title sync, LogRocket session replay.",
5
5
  "license": "MIT",
6
6
  "main": "helix-sdk.js",