@viu/emporix-sdk 2.5.0 → 2.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @viu/emporix-sdk
2
2
 
3
+ ## 2.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#92](https://github.com/viuteam/emporix-sdk/pull/92) [`45a2bd8`](https://github.com/viuteam/emporix-sdk/commit/45a2bd8d83cb46d775301790cb2efc60805efc90) Thanks [@amnael1](https://github.com/amnael1)! - Add opt-in reactive customer-token auto-refresh.
8
+
9
+ Core: `EmporixClient.setCustomerTokenRefresher(refresher)` registers a
10
+ single-flight `CustomerTokenRefresher`; on a `customer`-kind 401 the HTTP layer
11
+ refreshes once and retries. Off by default — the customer token stays
12
+ caller-owned.
13
+
14
+ React: `EmporixProvider` gains `autoRefreshCustomerToken` and
15
+ `onCustomerSessionExpired`. When enabled, a customer 401 is transparently
16
+ refreshed via the stored refresh token (anonymous-authorized
17
+ `GET /refreshauthtoken`) and the request is retried; B2B `legalEntityId` is
18
+ preserved.
19
+
3
20
  ## 2.5.0
4
21
 
5
22
  ### Minor Changes
@@ -1,4 +1,4 @@
1
- import { C as ClientContext, b as AuthContext } from './context-CgUWXqEt.cjs';
1
+ import { C as ClientContext, b as AuthContext } from './context-DJDTlhLW.cjs';
2
2
 
3
3
  type DistributionChannel = 'ASSORTMENT' | 'HOME_DELIVERY' | 'PICKUP';
4
4
  type Availability$1 = {
@@ -1,4 +1,4 @@
1
- import { C as ClientContext, b as AuthContext } from './context-CgUWXqEt.js';
1
+ import { C as ClientContext, b as AuthContext } from './context-DJDTlhLW.js';
2
2
 
3
3
  type DistributionChannel = 'ASSORTMENT' | 'HOME_DELIVERY' | 'PICKUP';
4
4
  type Availability$1 = {
package/dist/cart.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { C as ClientContext, b as AuthContext } from './context-CgUWXqEt.cjs';
1
+ import { C as ClientContext, b as AuthContext } from './context-DJDTlhLW.cjs';
2
2
 
3
3
  /**
4
4
  * Access control identifier that determines who can view or manage the cart.
package/dist/cart.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { C as ClientContext, b as AuthContext } from './context-CgUWXqEt.js';
1
+ import { C as ClientContext, b as AuthContext } from './context-DJDTlhLW.js';
2
2
 
3
3
  /**
4
4
  * Access control identifier that determines who can view or manage the cart.
@@ -1,4 +1,4 @@
1
- import { C as ClientContext, b as AuthContext, P as PaginatedItems } from './context-CgUWXqEt.cjs';
1
+ import { C as ClientContext, b as AuthContext, P as PaginatedItems } from './context-DJDTlhLW.cjs';
2
2
  import { Product } from './product.cjs';
3
3
 
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { C as ClientContext, b as AuthContext, P as PaginatedItems } from './context-CgUWXqEt.js';
1
+ import { C as ClientContext, b as AuthContext, P as PaginatedItems } from './context-DJDTlhLW.js';
2
2
  import { Product } from './product.js';
3
3
 
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { C as ClientContext, b as AuthContext } from './context-CgUWXqEt.cjs';
1
+ import { C as ClientContext, b as AuthContext } from './context-DJDTlhLW.cjs';
2
2
 
3
3
  /**
4
4
  * Information about the customer's address.
@@ -1,4 +1,4 @@
1
- import { C as ClientContext, b as AuthContext } from './context-CgUWXqEt.js';
1
+ import { C as ClientContext, b as AuthContext } from './context-DJDTlhLW.js';
2
2
 
3
3
  /**
4
4
  * Information about the customer's address.
@@ -1,4 +1,4 @@
1
- import { C as ClientContext, b as AuthContext } from './context-CgUWXqEt.cjs';
1
+ import { C as ClientContext, b as AuthContext } from './context-DJDTlhLW.cjs';
2
2
  import { L as LegalEntity, f as LegalEntityCreate, h as LegalEntityUpdate } from './types.gen-D1QuqPLs.cjs';
3
3
  export { A as AccountLimit, e as CustomerGroupRef, g as LegalEntityType, i as LegalInfo } from './types.gen-D1QuqPLs.cjs';
4
4
 
@@ -1,4 +1,4 @@
1
- import { C as ClientContext, b as AuthContext } from './context-CgUWXqEt.js';
1
+ import { C as ClientContext, b as AuthContext } from './context-DJDTlhLW.js';
2
2
  import { L as LegalEntity, f as LegalEntityCreate, h as LegalEntityUpdate } from './types.gen-D1QuqPLs.js';
3
3
  export { A as AccountLimit, e as CustomerGroupRef, g as LegalEntityType, i as LegalInfo } from './types.gen-D1QuqPLs.js';
4
4
 
@@ -1,4 +1,4 @@
1
- import { C as ClientContext, b as AuthContext } from './context-CgUWXqEt.cjs';
1
+ import { C as ClientContext, b as AuthContext } from './context-DJDTlhLW.cjs';
2
2
  import { C as ContactAssignment, a as ContactAssignmentCreate, c as ContactAssignmentUpdate } from './types.gen-D1QuqPLs.cjs';
3
3
  export { b as ContactAssignmentType } from './types.gen-D1QuqPLs.cjs';
4
4
 
@@ -1,4 +1,4 @@
1
- import { C as ClientContext, b as AuthContext } from './context-CgUWXqEt.js';
1
+ import { C as ClientContext, b as AuthContext } from './context-DJDTlhLW.js';
2
2
  import { C as ContactAssignment, a as ContactAssignmentCreate, c as ContactAssignmentUpdate } from './types.gen-D1QuqPLs.js';
3
3
  export { b as ContactAssignmentType } from './types.gen-D1QuqPLs.js';
4
4
 
@@ -161,6 +161,19 @@ type AuthContext = {
161
161
  kind: "raw";
162
162
  token: string;
163
163
  };
164
+ /**
165
+ * Supplies a fresh customer token when a `customer`-kind request 401s. The host
166
+ * (e.g. EmporixProvider) implements this; the SDK never refreshes the
167
+ * caller-owned customer token unless a refresher is registered.
168
+ */
169
+ interface CustomerTokenRefresher {
170
+ /**
171
+ * Called on a `customer`-kind 401. Receives the token that just failed;
172
+ * returns a fresh customer token to retry with, or `null` to give up (the
173
+ * 401 then propagates as EmporixAuthError).
174
+ */
175
+ refresh(expiredToken: string): Promise<string | null>;
176
+ }
164
177
  /** An obtained anonymous storefront session. */
165
178
  interface AnonymousSession {
166
179
  accessToken: string;
@@ -232,6 +245,20 @@ declare const auth: {
232
245
  };
233
246
  /** Resolves an {@link AuthContext} to a concrete bearer token. */
234
247
  declare function resolveToken(ctx: AuthContext, provider: TokenProvider): Promise<string>;
248
+ /**
249
+ * Late-bindable, single-flight holder for an optional
250
+ * {@link CustomerTokenRefresher}. Single-flight is required because Emporix
251
+ * rotates the refresh token on each refresh — concurrent refreshes would
252
+ * invalidate each other. Off (returns `null`) until a refresher is set.
253
+ */
254
+ declare class CustomerRefreshRegistry {
255
+ private refresher;
256
+ private inflight;
257
+ set(refresher: CustomerTokenRefresher | null): void;
258
+ get enabled(): boolean;
259
+ /** Concurrent callers share one in-flight refresh. */
260
+ refresh(expiredToken: string): Promise<string | null>;
261
+ }
235
262
  /** SDK-owned token provider: client-credentials service tokens + anonymous session. */
236
263
  declare class DefaultTokenProvider implements TokenProvider {
237
264
  private readonly cfg;
@@ -290,6 +317,8 @@ interface HttpClientOptions {
290
317
  };
291
318
  /** Override the retry backoff delay (ms → Promise). Defaults to a real timer. */
292
319
  sleep?: (ms: number) => Promise<void>;
320
+ /** Opt-in customer-token refresher registry (off unless a refresher is set). */
321
+ customerRefresh?: CustomerRefreshRegistry;
293
322
  }
294
323
  /** Fetch wrapper: auth resolution, JSON parsing, typed error mapping, logging. */
295
324
  declare class HttpClient {
@@ -341,4 +370,4 @@ interface PaginatedItems<T> {
341
370
  */
342
371
  declare function iterateAll<T>(fetchPage: (pageNumber: number) => Promise<PaginatedItems<T>>, start?: number): AsyncIterable<T>;
343
372
 
344
- export { type AnonymousSession as A, type ClientContext as C, DEFAULT_HOST as D, type EmporixConfig as E, HttpClient as H, LEVEL as L, type PaginatedItems as P, type RequestOptions as R, type ServiceCredentials as S, type TokenProvider as T, type AnonymousSessionStore as a, type AuthContext as b, type AuthKind as c, DefaultTokenProvider as d, type HttpClientOptions as e, LevelResolver as f, type LogFields as g, type LogLevel as h, type Logger as i, type LoggerConfig as j, type LoggerObjectConfig as k, type ResolvedConfig as l, type ServiceName as m, type StorefrontCredentials as n, auth as o, createConsoleLogger as p, createNoopLogger as q, iterateAll as r, redact as s, resolveToken as t, validateConfig as v };
373
+ export { type AnonymousSession as A, type ClientContext as C, DEFAULT_HOST as D, type EmporixConfig as E, HttpClient as H, LEVEL as L, type PaginatedItems as P, type RequestOptions as R, type ServiceCredentials as S, type TokenProvider as T, type AnonymousSessionStore as a, type AuthContext as b, type AuthKind as c, CustomerRefreshRegistry as d, type CustomerTokenRefresher as e, DefaultTokenProvider as f, type HttpClientOptions as g, LevelResolver as h, type LogFields as i, type LogLevel as j, type Logger as k, type LoggerConfig as l, type LoggerObjectConfig as m, type ResolvedConfig as n, type ServiceName as o, type StorefrontCredentials as p, auth as q, createConsoleLogger as r, createNoopLogger as s, iterateAll as t, redact as u, resolveToken as v, validateConfig as w };
@@ -161,6 +161,19 @@ type AuthContext = {
161
161
  kind: "raw";
162
162
  token: string;
163
163
  };
164
+ /**
165
+ * Supplies a fresh customer token when a `customer`-kind request 401s. The host
166
+ * (e.g. EmporixProvider) implements this; the SDK never refreshes the
167
+ * caller-owned customer token unless a refresher is registered.
168
+ */
169
+ interface CustomerTokenRefresher {
170
+ /**
171
+ * Called on a `customer`-kind 401. Receives the token that just failed;
172
+ * returns a fresh customer token to retry with, or `null` to give up (the
173
+ * 401 then propagates as EmporixAuthError).
174
+ */
175
+ refresh(expiredToken: string): Promise<string | null>;
176
+ }
164
177
  /** An obtained anonymous storefront session. */
165
178
  interface AnonymousSession {
166
179
  accessToken: string;
@@ -232,6 +245,20 @@ declare const auth: {
232
245
  };
233
246
  /** Resolves an {@link AuthContext} to a concrete bearer token. */
234
247
  declare function resolveToken(ctx: AuthContext, provider: TokenProvider): Promise<string>;
248
+ /**
249
+ * Late-bindable, single-flight holder for an optional
250
+ * {@link CustomerTokenRefresher}. Single-flight is required because Emporix
251
+ * rotates the refresh token on each refresh — concurrent refreshes would
252
+ * invalidate each other. Off (returns `null`) until a refresher is set.
253
+ */
254
+ declare class CustomerRefreshRegistry {
255
+ private refresher;
256
+ private inflight;
257
+ set(refresher: CustomerTokenRefresher | null): void;
258
+ get enabled(): boolean;
259
+ /** Concurrent callers share one in-flight refresh. */
260
+ refresh(expiredToken: string): Promise<string | null>;
261
+ }
235
262
  /** SDK-owned token provider: client-credentials service tokens + anonymous session. */
236
263
  declare class DefaultTokenProvider implements TokenProvider {
237
264
  private readonly cfg;
@@ -290,6 +317,8 @@ interface HttpClientOptions {
290
317
  };
291
318
  /** Override the retry backoff delay (ms → Promise). Defaults to a real timer. */
292
319
  sleep?: (ms: number) => Promise<void>;
320
+ /** Opt-in customer-token refresher registry (off unless a refresher is set). */
321
+ customerRefresh?: CustomerRefreshRegistry;
293
322
  }
294
323
  /** Fetch wrapper: auth resolution, JSON parsing, typed error mapping, logging. */
295
324
  declare class HttpClient {
@@ -341,4 +370,4 @@ interface PaginatedItems<T> {
341
370
  */
342
371
  declare function iterateAll<T>(fetchPage: (pageNumber: number) => Promise<PaginatedItems<T>>, start?: number): AsyncIterable<T>;
343
372
 
344
- export { type AnonymousSession as A, type ClientContext as C, DEFAULT_HOST as D, type EmporixConfig as E, HttpClient as H, LEVEL as L, type PaginatedItems as P, type RequestOptions as R, type ServiceCredentials as S, type TokenProvider as T, type AnonymousSessionStore as a, type AuthContext as b, type AuthKind as c, DefaultTokenProvider as d, type HttpClientOptions as e, LevelResolver as f, type LogFields as g, type LogLevel as h, type Logger as i, type LoggerConfig as j, type LoggerObjectConfig as k, type ResolvedConfig as l, type ServiceName as m, type StorefrontCredentials as n, auth as o, createConsoleLogger as p, createNoopLogger as q, iterateAll as r, redact as s, resolveToken as t, validateConfig as v };
373
+ export { type AnonymousSession as A, type ClientContext as C, DEFAULT_HOST as D, type EmporixConfig as E, HttpClient as H, LEVEL as L, type PaginatedItems as P, type RequestOptions as R, type ServiceCredentials as S, type TokenProvider as T, type AnonymousSessionStore as a, type AuthContext as b, type AuthKind as c, CustomerRefreshRegistry as d, type CustomerTokenRefresher as e, DefaultTokenProvider as f, type HttpClientOptions as g, LevelResolver as h, type LogFields as i, type LogLevel as j, type Logger as k, type LoggerConfig as l, type LoggerObjectConfig as m, type ResolvedConfig as n, type ServiceName as o, type StorefrontCredentials as p, auth as q, createConsoleLogger as r, createNoopLogger as s, iterateAll as t, redact as u, resolveToken as v, validateConfig as w };
@@ -1,4 +1,4 @@
1
- import { C as ClientContext, b as AuthContext } from './context-CgUWXqEt.cjs';
1
+ import { C as ClientContext, b as AuthContext } from './context-DJDTlhLW.cjs';
2
2
 
3
3
  /**
4
4
  * Hand-written mirror of the minimal IAM Service group shape needed for
@@ -1,4 +1,4 @@
1
- import { C as ClientContext, b as AuthContext } from './context-CgUWXqEt.js';
1
+ import { C as ClientContext, b as AuthContext } from './context-DJDTlhLW.js';
2
2
 
3
3
  /**
4
4
  * Hand-written mirror of the minimal IAM Service group shape needed for
@@ -1,4 +1,4 @@
1
- import { C as ClientContext, A as AnonymousSession, b as AuthContext } from './context-CgUWXqEt.cjs';
1
+ import { C as ClientContext, A as AnonymousSession, b as AuthContext } from './context-DJDTlhLW.cjs';
2
2
 
3
3
  type AccountId = {
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { C as ClientContext, A as AnonymousSession, b as AuthContext } from './context-CgUWXqEt.js';
1
+ import { C as ClientContext, A as AnonymousSession, b as AuthContext } from './context-DJDTlhLW.js';
2
2
 
3
3
  type AccountId = {
4
4
  /**
package/dist/index.cjs CHANGED
@@ -366,6 +366,26 @@ async function resolveToken(ctx, provider) {
366
366
  return ctx.token;
367
367
  }
368
368
  }
369
+ var CustomerRefreshRegistry = class {
370
+ refresher = null;
371
+ inflight = null;
372
+ set(refresher) {
373
+ this.refresher = refresher;
374
+ }
375
+ get enabled() {
376
+ return this.refresher !== null;
377
+ }
378
+ /** Concurrent callers share one in-flight refresh. */
379
+ refresh(expiredToken) {
380
+ if (!this.refresher) return Promise.resolve(null);
381
+ if (this.inflight) return this.inflight;
382
+ const p = Promise.resolve(this.refresher.refresh(expiredToken)).finally(() => {
383
+ this.inflight = null;
384
+ });
385
+ this.inflight = p;
386
+ return p;
387
+ }
388
+ };
369
389
  var DefaultTokenProvider = class {
370
390
  constructor(cfg) {
371
391
  this.cfg = cfg;
@@ -567,8 +587,10 @@ var HttpClient = class {
567
587
  const sdkManaged = o.auth.kind === "service" || o.auth.kind === "anonymous";
568
588
  const maxAttempts = this.opts.retry.maxAttempts;
569
589
  let reauthed = false;
590
+ let customerToken = o.auth.kind === "customer" ? o.auth.token : void 0;
591
+ let customerReauthed = false;
570
592
  for (let attempt = 1; ; attempt++) {
571
- const token = await resolveToken(o.auth, this.opts.provider);
593
+ const token = customerToken ?? await resolveToken(o.auth, this.opts.provider);
572
594
  log.debug("http request", {
573
595
  authKind: o.auth.kind,
574
596
  method: o.method,
@@ -620,6 +642,15 @@ var HttpClient = class {
620
642
  log.warn("sdk-managed 401, re-authing once", { authKind: o.auth.kind });
621
643
  continue;
622
644
  }
645
+ if (o.auth.kind === "customer" && !customerReauthed && this.opts.customerRefresh?.enabled) {
646
+ customerReauthed = true;
647
+ const fresh = await this.opts.customerRefresh.refresh(customerToken);
648
+ if (fresh) {
649
+ customerToken = fresh;
650
+ log.warn("customer 401, refreshed once", { authKind: o.auth.kind });
651
+ continue;
652
+ }
653
+ }
623
654
  throw errorFromResponse(res.status, `${o.method} ${o.path} \u2192 401`, parsed);
624
655
  }
625
656
  const retryable = res.status >= 500 || res.status === 429;
@@ -4942,7 +4973,7 @@ var ApprovalService = class {
4942
4973
  };
4943
4974
 
4944
4975
  // package.json
4945
- var version = "2.5.0";
4976
+ var version = "2.6.0";
4946
4977
 
4947
4978
  // src/version.ts
4948
4979
  var SDK_VERSION = version;
@@ -5009,6 +5040,7 @@ var EmporixClient = class {
5009
5040
  */
5010
5041
  config;
5011
5042
  resolver;
5043
+ customerRefresh;
5012
5044
  constructor(config) {
5013
5045
  const cfg = validateConfig(config);
5014
5046
  this.tenant = cfg.tenant;
@@ -5030,6 +5062,8 @@ var EmporixClient = class {
5030
5062
  });
5031
5063
  const tokenProvider = cfg.tokenProvider ?? new DefaultTokenProvider(cfg);
5032
5064
  this.tokenProvider = tokenProvider;
5065
+ const customerRefresh = new CustomerRefreshRegistry();
5066
+ this.customerRefresh = customerRefresh;
5033
5067
  const mk = (service) => ({
5034
5068
  tenant: cfg.tenant,
5035
5069
  tokenProvider,
@@ -5039,7 +5073,8 @@ var EmporixClient = class {
5039
5073
  provider: tokenProvider,
5040
5074
  logger: root.child({ service: "http" }),
5041
5075
  retry: cfg.retry,
5042
- timeouts: cfg.timeouts
5076
+ timeouts: cfg.timeouts,
5077
+ customerRefresh
5043
5078
  })
5044
5079
  });
5045
5080
  this.customers = new CustomerService(mk("customer"));
@@ -5098,6 +5133,15 @@ var EmporixClient = class {
5098
5133
  getLogLevel(service) {
5099
5134
  return this.resolver.get(service);
5100
5135
  }
5136
+ /**
5137
+ * Registers (or clears with `null`) a customer-token refresher. When set, a
5138
+ * `customer`-kind 401 triggers one refresh-and-retry. Off by default — the
5139
+ * customer token stays caller-owned. The React `EmporixProvider` wires this
5140
+ * automatically via `autoRefreshCustomerToken`.
5141
+ */
5142
+ setCustomerTokenRefresher(refresher) {
5143
+ this.customerRefresh.set(refresher);
5144
+ }
5101
5145
  };
5102
5146
 
5103
5147
  exports.AiService = AiService;
@@ -5116,6 +5160,7 @@ exports.CouponService = CouponService;
5116
5160
  exports.CurrencyService = CurrencyService;
5117
5161
  exports.CustomerAdminService = CustomerAdminService;
5118
5162
  exports.CustomerGroupsService = CustomerGroupsService;
5163
+ exports.CustomerRefreshRegistry = CustomerRefreshRegistry;
5119
5164
  exports.CustomerService = CustomerService;
5120
5165
  exports.DEFAULT_HOST = DEFAULT_HOST;
5121
5166
  exports.DefaultTokenProvider = DefaultTokenProvider;