@viu/emporix-sdk 2.15.0 → 2.16.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,14 @@
1
1
  # @viu/emporix-sdk
2
2
 
3
+ ## 2.16.0
4
+
5
+ ### Patch Changes
6
+
7
+ - [#134](https://github.com/viuteam/emporix-sdk/pull/134) [`f3f29aa`](https://github.com/viuteam/emporix-sdk/commit/f3f29aab5dd364d4ebaa5edf5ace524222dad6be) Thanks [@amnael1](https://github.com/amnael1)! - `client.payments.listPaymentModes` no longer requires a customer token. It now
8
+ defaults to an anonymous context, matching the public frontend payment-modes
9
+ endpoint (which needs a bearer token but no customer scope), so storefronts can
10
+ list configured payment modes for guests as well as logged-in customers.
11
+
3
12
  ## 2.15.0
4
13
 
5
14
  ### Minor Changes
@@ -0,0 +1,280 @@
1
+ import { requireCustomer } from './chunk-3BDDYX3V.js';
2
+ import { EmporixAuthError, EmporixTimeoutError, EmporixNetworkError } from './chunk-UWGGF4MC.js';
3
+
4
+ // src/core/auth.ts
5
+ var auth = {
6
+ /** Service/custom credential set (default `backend`). */
7
+ service: (credentials) => credentials === void 0 ? { kind: "service" } : { kind: "service", credentials },
8
+ /** Cached anonymous storefront token. */
9
+ anonymous: () => ({ kind: "anonymous" }),
10
+ /** Caller-owned customer bearer token. */
11
+ customer: (token) => ({ kind: "customer", token }),
12
+ /** Exact token, no transformation (SSO / token-exchange). */
13
+ raw: (token) => ({ kind: "raw", token })
14
+ };
15
+ async function resolveToken(ctx, provider) {
16
+ switch (ctx.kind) {
17
+ case "service":
18
+ return provider.getToken(ctx.credentials ?? "backend");
19
+ case "anonymous":
20
+ return (await provider.getAnonymousToken()).accessToken;
21
+ case "customer":
22
+ case "raw":
23
+ return ctx.token;
24
+ }
25
+ }
26
+ var CustomerRefreshRegistry = class {
27
+ refresher = null;
28
+ inflight = null;
29
+ set(refresher) {
30
+ this.refresher = refresher;
31
+ }
32
+ get enabled() {
33
+ return this.refresher !== null;
34
+ }
35
+ /** Concurrent callers share one in-flight refresh. */
36
+ refresh(expiredToken) {
37
+ if (!this.refresher) return Promise.resolve(null);
38
+ if (this.inflight) return this.inflight;
39
+ const p = Promise.resolve(this.refresher.refresh(expiredToken)).finally(() => {
40
+ this.inflight = null;
41
+ });
42
+ this.inflight = p;
43
+ return p;
44
+ }
45
+ };
46
+ var DefaultTokenProvider = class {
47
+ constructor(cfg) {
48
+ this.cfg = cfg;
49
+ }
50
+ cfg;
51
+ serviceCache = /* @__PURE__ */ new Map();
52
+ serviceLocks = /* @__PURE__ */ new Map();
53
+ anon;
54
+ anonLock;
55
+ anonStore;
56
+ contextOverride;
57
+ refreshListeners = /* @__PURE__ */ new Set();
58
+ onRefresh(listener) {
59
+ this.refreshListeners.add(listener);
60
+ return () => {
61
+ this.refreshListeners.delete(listener);
62
+ };
63
+ }
64
+ notifyRefresh(kind, success) {
65
+ for (const l of this.refreshListeners) {
66
+ try {
67
+ l({ kind, success });
68
+ } catch {
69
+ }
70
+ }
71
+ }
72
+ attachAnonymousStore(store) {
73
+ this.anonStore = store;
74
+ if (!this.anon) {
75
+ const persisted = store.read();
76
+ if (persisted) {
77
+ this.anon = {
78
+ accessToken: "",
79
+ refreshToken: persisted.refreshToken,
80
+ sessionId: persisted.sessionId,
81
+ expiresIn: 0,
82
+ expiresAt: 0
83
+ };
84
+ }
85
+ }
86
+ }
87
+ creds(set) {
88
+ if (set === "backend") {
89
+ const b = this.cfg.credentials.backend;
90
+ if (!b?.clientId || !b.secret) {
91
+ throw new EmporixAuthError(
92
+ "A 'service' AuthContext was used but credentials.backend is not configured"
93
+ );
94
+ }
95
+ return b;
96
+ }
97
+ const c = this.cfg.credentials.custom?.[set];
98
+ if (!c) throw new Error(`Unknown credential set "${set}"`);
99
+ return c;
100
+ }
101
+ fresh(e) {
102
+ if (!e) return false;
103
+ const now = Date.now();
104
+ if (now - e.obtainedAt >= this.cfg.cache.maxLifetimeSeconds * 1e3) return false;
105
+ return now < e.expiresAt;
106
+ }
107
+ async getToken(set) {
108
+ this.creds(set);
109
+ const cached = this.serviceCache.get(set);
110
+ if (this.fresh(cached)) return cached.token;
111
+ const inflight = this.serviceLocks.get(set);
112
+ if (inflight) return inflight;
113
+ const p = this.requestServiceToken(set).finally(() => this.serviceLocks.delete(set));
114
+ this.serviceLocks.set(set, p);
115
+ return p;
116
+ }
117
+ /**
118
+ * fetch with the configured read timeout. Token endpoints sit in front of
119
+ * single-flight locks — one hung call would otherwise block every request
120
+ * on this credential set forever.
121
+ */
122
+ async boundedFetch(url, init = {}) {
123
+ const ms = this.cfg.timeouts?.readMs ?? 6e4;
124
+ const controller = new AbortController();
125
+ const timer = setTimeout(() => controller.abort(), ms);
126
+ try {
127
+ return await fetch(url, { ...init, signal: controller.signal });
128
+ } catch (err) {
129
+ if (err.name === "AbortError") {
130
+ throw new EmporixTimeoutError(`token request timed out after ${ms}ms`);
131
+ }
132
+ throw new EmporixNetworkError(`token request network failure: ${err.message}`);
133
+ } finally {
134
+ clearTimeout(timer);
135
+ }
136
+ }
137
+ async requestServiceToken(set) {
138
+ const c = this.creds(set);
139
+ const body = new URLSearchParams({
140
+ grant_type: "client_credentials",
141
+ client_id: c.clientId,
142
+ client_secret: c.secret
143
+ });
144
+ if (c.scope) body.set("scope", c.scope);
145
+ const res = await this.boundedFetch(`${this.cfg.host}/oauth/token`, {
146
+ method: "POST",
147
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
148
+ body
149
+ });
150
+ const json = await res.json().catch(() => ({}));
151
+ if (!res.ok) {
152
+ throw new EmporixAuthError(`Token request failed for "${set}"`, res.status, json);
153
+ }
154
+ const obtainedAt = Date.now();
155
+ const ttl = Number(json.expires_in ?? 3600);
156
+ this.serviceCache.set(set, {
157
+ token: json.access_token,
158
+ obtainedAt,
159
+ expiresAt: obtainedAt + (ttl - this.cfg.cache.expirationBufferSeconds) * 1e3
160
+ });
161
+ return this.serviceCache.get(set).token;
162
+ }
163
+ invalidate(set) {
164
+ this.serviceCache.delete(set);
165
+ }
166
+ anonFresh() {
167
+ return !!this.anon && Date.now() < this.anon.expiresAt;
168
+ }
169
+ async getAnonymousToken() {
170
+ if (this.anonFresh()) return this.stripExpiry(this.anon);
171
+ if (this.anonLock) return this.anonLock;
172
+ const canRefresh = !!this.anon?.refreshToken;
173
+ const obtain = canRefresh ? this.fetchAnonymous("refresh").catch(() => this.fetchAnonymous("login")) : this.fetchAnonymous("login");
174
+ const p = obtain.finally(() => {
175
+ this.anonLock = void 0;
176
+ });
177
+ this.anonLock = p;
178
+ return p;
179
+ }
180
+ /** Refreshes the anonymous session, preserving its sessionId. */
181
+ async refreshAnonymous() {
182
+ if (!this.anon) return this.getAnonymousToken();
183
+ return this.fetchAnonymous("refresh");
184
+ }
185
+ invalidateAnonymous() {
186
+ this.anon = void 0;
187
+ this.anonStore?.write(null);
188
+ }
189
+ setAnonymousContext(ctx) {
190
+ const base = this.contextOverride ?? this.cfg.credentials.storefront?.context ?? {};
191
+ this.contextOverride = { ...base, ...ctx };
192
+ this.invalidateAnonymous();
193
+ }
194
+ /** Force a stale access token while keeping the refresh token + sessionId. */
195
+ expireAnonymous() {
196
+ if (this.anon) this.anon = { ...this.anon, expiresAt: 0 };
197
+ }
198
+ stripExpiry(s) {
199
+ const { expiresAt: _expiresAt, ...rest } = s;
200
+ return rest;
201
+ }
202
+ async fetchAnonymous(mode) {
203
+ const sf = this.cfg.credentials.storefront;
204
+ if (!sf?.clientId) {
205
+ throw new Error("credentials.storefront.clientId is required for anonymous tokens");
206
+ }
207
+ const url = new URL(`${this.cfg.host}/customerlogin/auth/anonymous/${mode}`);
208
+ url.searchParams.set("tenant", this.cfg.tenant);
209
+ url.searchParams.set("client_id", sf.clientId);
210
+ const c = this.contextOverride ?? sf.context;
211
+ if (c?.currency) url.searchParams.set("currency", c.currency);
212
+ if (c?.siteCode) url.searchParams.set("siteCode", c.siteCode);
213
+ if (c?.targetLocation) url.searchParams.set("targetLocation", c.targetLocation);
214
+ if (mode === "refresh" && this.anon) {
215
+ url.searchParams.set("refresh_token", this.anon.refreshToken);
216
+ }
217
+ try {
218
+ const res = await this.boundedFetch(url, { method: "GET" });
219
+ const json = await res.json().catch(() => ({}));
220
+ if (!res.ok) {
221
+ this.notifyRefresh("anonymous", false);
222
+ throw new EmporixAuthError(`Anonymous token ${mode} failed`, res.status, json);
223
+ }
224
+ const obtainedAt = Date.now();
225
+ this.anon = {
226
+ accessToken: json.access_token,
227
+ refreshToken: json.refresh_token,
228
+ sessionId: json.sessionId,
229
+ expiresIn: json.expires_in,
230
+ expiresAt: obtainedAt + (json.expires_in - this.cfg.cache.expirationBufferSeconds) * 1e3
231
+ };
232
+ this.anonStore?.write({
233
+ refreshToken: this.anon.refreshToken,
234
+ sessionId: this.anon.sessionId
235
+ });
236
+ this.notifyRefresh("anonymous", true);
237
+ return this.stripExpiry(this.anon);
238
+ } catch (err) {
239
+ if (!(err instanceof EmporixAuthError)) {
240
+ this.notifyRefresh("anonymous", false);
241
+ }
242
+ throw err;
243
+ }
244
+ }
245
+ };
246
+
247
+ // src/services/payment.ts
248
+ var ANON = auth.anonymous();
249
+ var PaymentGatewayService = class {
250
+ constructor(ctx) {
251
+ this.ctx = ctx;
252
+ }
253
+ ctx;
254
+ static channel = "payment";
255
+ /**
256
+ * Lists configured frontend payment modes. The endpoint requires a bearer
257
+ * token but no customer scope ("No scope required"), so it defaults to an
258
+ * anonymous context and works for guests and logged-in customers alike.
259
+ */
260
+ async listPaymentModes(authCtx = ANON) {
261
+ return this.ctx.http.request({
262
+ method: "GET",
263
+ path: `/payment-gateway/${this.ctx.tenant}/paymentmodes/frontend`,
264
+ auth: authCtx
265
+ });
266
+ }
267
+ /** Authorizes a post-checkout (deferred) payment for an existing order. */
268
+ async authorize(input, authCtx) {
269
+ return this.ctx.http.request({
270
+ method: "POST",
271
+ path: `/payment-gateway/${this.ctx.tenant}/payment/frontend/authorize`,
272
+ auth: requireCustomer(authCtx),
273
+ body: input
274
+ });
275
+ }
276
+ };
277
+
278
+ export { CustomerRefreshRegistry, DefaultTokenProvider, PaymentGatewayService, auth, resolveToken };
279
+ //# sourceMappingURL=chunk-PYLWZZC4.js.map
280
+ //# sourceMappingURL=chunk-PYLWZZC4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/auth.ts","../src/services/payment.ts"],"names":[],"mappings":";;;;AA4FO,IAAM,IAAA,GAAO;AAAA;AAAA,EAElB,OAAA,EAAS,CAAC,WAAA,KACR,WAAA,KAAgB,MAAA,GAAY,EAAE,IAAA,EAAM,SAAA,EAAU,GAAI,EAAE,IAAA,EAAM,SAAA,EAAW,WAAA,EAAY;AAAA;AAAA,EAEnF,SAAA,EAAW,OAAoB,EAAE,IAAA,EAAM,WAAA,EAAY,CAAA;AAAA;AAAA,EAEnD,UAAU,CAAC,KAAA,MAAgC,EAAE,IAAA,EAAM,YAAY,KAAA,EAAM,CAAA;AAAA;AAAA,EAErE,KAAK,CAAC,KAAA,MAAgC,EAAE,IAAA,EAAM,OAAO,KAAA,EAAM;AAC7D;AAGA,eAAsB,YAAA,CAAa,KAAkB,QAAA,EAA0C;AAC7F,EAAA,QAAQ,IAAI,IAAA;AAAM,IAChB,KAAK,SAAA;AACH,MAAA,OAAO,QAAA,CAAS,QAAA,CAAS,GAAA,CAAI,WAAA,IAAe,SAAS,CAAA;AAAA,IACvD,KAAK,WAAA;AACH,MAAA,OAAA,CAAQ,MAAM,QAAA,CAAS,iBAAA,EAAkB,EAAG,WAAA;AAAA,IAC9C,KAAK,UAAA;AAAA,IACL,KAAK,KAAA;AACH,MAAA,OAAO,GAAA,CAAI,KAAA;AAAA;AAEjB;AAQO,IAAM,0BAAN,MAA8B;AAAA,EAC3B,SAAA,GAA2C,IAAA;AAAA,EAC3C,QAAA,GAA0C,IAAA;AAAA,EAElD,IAAI,SAAA,EAAgD;AAClD,IAAA,IAAA,CAAK,SAAA,GAAY,SAAA;AAAA,EACnB;AAAA,EAEA,IAAI,OAAA,GAAmB;AACrB,IAAA,OAAO,KAAK,SAAA,KAAc,IAAA;AAAA,EAC5B;AAAA;AAAA,EAGA,QAAQ,YAAA,EAA8C;AACpD,IAAA,IAAI,CAAC,IAAA,CAAK,SAAA,EAAW,OAAO,OAAA,CAAQ,QAAQ,IAAI,CAAA;AAChD,IAAA,IAAI,IAAA,CAAK,QAAA,EAAU,OAAO,IAAA,CAAK,QAAA;AAC/B,IAAA,MAAM,CAAA,GAAI,OAAA,CAAQ,OAAA,CAAQ,IAAA,CAAK,SAAA,CAAU,QAAQ,YAAY,CAAC,CAAA,CAAE,OAAA,CAAQ,MAAM;AAC5E,MAAA,IAAA,CAAK,QAAA,GAAW,IAAA;AAAA,IAClB,CAAC,CAAA;AACD,IAAA,IAAA,CAAK,QAAA,GAAW,CAAA;AAChB,IAAA,OAAO,CAAA;AAAA,EACT;AACF;AASO,IAAM,uBAAN,MAAoD;AAAA,EAazD,YAA6B,GAAA,EAAqB;AAArB,IAAA,IAAA,CAAA,GAAA,GAAA,GAAA;AAAA,EAAsB;AAAA,EAAtB,GAAA;AAAA,EAZZ,YAAA,uBAAmB,GAAA,EAAwB;AAAA,EAC3C,YAAA,uBAAmB,GAAA,EAA6B;AAAA,EACzD,IAAA;AAAA,EACA,QAAA;AAAA,EACA,SAAA;AAAA,EACA,eAAA;AAAA,EAGS,gBAAA,uBAAuB,GAAA,EAEtC;AAAA,EAIF,UACE,QAAA,EACY;AACZ,IAAA,IAAA,CAAK,gBAAA,CAAiB,IAAI,QAAQ,CAAA;AAClC,IAAA,OAAO,MAAM;AACX,MAAA,IAAA,CAAK,gBAAA,CAAiB,OAAO,QAAQ,CAAA;AAAA,IACvC,CAAA;AAAA,EACF;AAAA,EAEQ,aAAA,CAAc,MAAgC,OAAA,EAAwB;AAC5E,IAAA,KAAA,MAAW,CAAA,IAAK,KAAK,gBAAA,EAAkB;AACrC,MAAA,IAAI;AACF,QAAA,CAAA,CAAE,EAAE,IAAA,EAAM,OAAA,EAAS,CAAA;AAAA,MACrB,CAAA,CAAA,MAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAAA,EAEA,qBAAqB,KAAA,EAAoC;AACvD,IAAA,IAAA,CAAK,SAAA,GAAY,KAAA;AAIjB,IAAA,IAAI,CAAC,KAAK,IAAA,EAAM;AACd,MAAA,MAAM,SAAA,GAAY,MAAM,IAAA,EAAK;AAC7B,MAAA,IAAI,SAAA,EAAW;AACb,QAAA,IAAA,CAAK,IAAA,GAAO;AAAA,UACV,WAAA,EAAa,EAAA;AAAA,UACb,cAAc,SAAA,CAAU,YAAA;AAAA,UACxB,WAAW,SAAA,CAAU,SAAA;AAAA,UACrB,SAAA,EAAW,CAAA;AAAA,UACX,SAAA,EAAW;AAAA,SACb;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,MAAM,GAAA,EAAiC;AAC7C,IAAA,IAAI,QAAQ,SAAA,EAAW;AACrB,MAAA,MAAM,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,WAAA,CAAY,OAAA;AAC/B,MAAA,IAAI,CAAC,CAAA,EAAG,QAAA,IAAY,CAAC,EAAE,MAAA,EAAQ;AAC7B,QAAA,MAAM,IAAI,gBAAA;AAAA,UACR;AAAA,SACF;AAAA,MACF;AACA,MAAA,OAAO,CAAA;AAAA,IACT;AACA,IAAA,MAAM,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,WAAA,CAAY,SAAS,GAAG,CAAA;AAC3C,IAAA,IAAI,CAAC,CAAA,EAAG,MAAM,IAAI,KAAA,CAAM,CAAA,wBAAA,EAA2B,GAAG,CAAA,CAAA,CAAG,CAAA;AACzD,IAAA,OAAO,CAAA;AAAA,EACT;AAAA,EAEQ,MAAM,CAAA,EAAoC;AAChD,IAAA,IAAI,CAAC,GAAG,OAAO,KAAA;AACf,IAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,IAAA,IAAI,GAAA,GAAM,EAAE,UAAA,IAAc,IAAA,CAAK,IAAI,KAAA,CAAM,kBAAA,GAAqB,KAAM,OAAO,KAAA;AAC3E,IAAA,OAAO,MAAM,CAAA,CAAE,SAAA;AAAA,EACjB;AAAA,EAEA,MAAM,SAAS,GAAA,EAA8B;AAE3C,IAAA,IAAA,CAAK,MAAM,GAAG,CAAA;AACd,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,YAAA,CAAa,GAAA,CAAI,GAAG,CAAA;AACxC,IAAA,IAAI,IAAA,CAAK,KAAA,CAAM,MAAM,CAAA,SAAU,MAAA,CAAQ,KAAA;AACvC,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,YAAA,CAAa,GAAA,CAAI,GAAG,CAAA;AAC1C,IAAA,IAAI,UAAU,OAAO,QAAA;AACrB,IAAA,MAAM,CAAA,GAAI,IAAA,CAAK,mBAAA,CAAoB,GAAG,CAAA,CAAE,OAAA,CAAQ,MAAM,IAAA,CAAK,YAAA,CAAa,MAAA,CAAO,GAAG,CAAC,CAAA;AACnF,IAAA,IAAA,CAAK,YAAA,CAAa,GAAA,CAAI,GAAA,EAAK,CAAC,CAAA;AAC5B,IAAA,OAAO,CAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,YAAA,CAAa,GAAA,EAAmB,IAAA,GAAoB,EAAC,EAAsB;AACvF,IAAA,MAAM,EAAA,GAAK,IAAA,CAAK,GAAA,CAAI,QAAA,EAAU,MAAA,IAAU,GAAA;AACxC,IAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,IAAA,MAAM,QAAQ,UAAA,CAAW,MAAM,UAAA,CAAW,KAAA,IAAS,EAAE,CAAA;AACrD,IAAA,IAAI;AACF,MAAA,OAAO,MAAM,MAAM,GAAA,EAAK,EAAE,GAAG,IAAA,EAAM,MAAA,EAAQ,UAAA,CAAW,MAAA,EAAQ,CAAA;AAAA,IAChE,SAAS,GAAA,EAAK;AACZ,MAAA,IAAK,GAAA,CAAc,SAAS,YAAA,EAAc;AACxC,QAAA,MAAM,IAAI,mBAAA,CAAoB,CAAA,8BAAA,EAAiC,EAAE,CAAA,EAAA,CAAI,CAAA;AAAA,MACvE;AACA,MAAA,MAAM,IAAI,mBAAA,CAAoB,CAAA,+BAAA,EAAmC,GAAA,CAAc,OAAO,CAAA,CAAE,CAAA;AAAA,IAC1F,CAAA,SAAE;AACA,MAAA,YAAA,CAAa,KAAK,CAAA;AAAA,IACpB;AAAA,EACF;AAAA,EAEA,MAAc,oBAAoB,GAAA,EAA8B;AAC9D,IAAA,MAAM,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AACxB,IAAA,MAAM,IAAA,GAAO,IAAI,eAAA,CAAgB;AAAA,MAC/B,UAAA,EAAY,oBAAA;AAAA,MACZ,WAAW,CAAA,CAAE,QAAA;AAAA,MACb,eAAe,CAAA,CAAE;AAAA,KAClB,CAAA;AACD,IAAA,IAAI,EAAE,KAAA,EAAO,IAAA,CAAK,GAAA,CAAI,OAAA,EAAS,EAAE,KAAK,CAAA;AACtC,IAAA,MAAM,GAAA,GAAM,MAAM,IAAA,CAAK,YAAA,CAAa,GAAG,IAAA,CAAK,GAAA,CAAI,IAAI,CAAA,YAAA,CAAA,EAAgB;AAAA,MAClE,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS,EAAE,cAAA,EAAgB,mCAAA,EAAoC;AAAA,MAC/D;AAAA,KACD,CAAA;AACD,IAAA,MAAM,IAAA,GAAQ,MAAM,GAAA,CAAI,IAAA,GAAO,KAAA,CAAM,OAAO,EAAC,CAAE,CAAA;AAC/C,IAAA,IAAI,CAAC,IAAI,EAAA,EAAI;AACX,MAAA,MAAM,IAAI,gBAAA,CAAiB,CAAA,0BAAA,EAA6B,GAAG,CAAA,CAAA,CAAA,EAAK,GAAA,CAAI,QAAQ,IAAI,CAAA;AAAA,IAClF;AACA,IAAA,MAAM,UAAA,GAAa,KAAK,GAAA,EAAI;AAC5B,IAAA,MAAM,GAAA,GAAM,MAAA,CAAQ,IAAA,CAAK,UAAA,IAAqC,IAAI,CAAA;AAClE,IAAA,IAAA,CAAK,YAAA,CAAa,IAAI,GAAA,EAAK;AAAA,MACzB,OAAO,IAAA,CAAK,YAAA;AAAA,MACZ,UAAA;AAAA,MACA,WAAW,UAAA,GAAA,CAAc,GAAA,GAAM,IAAA,CAAK,GAAA,CAAI,MAAM,uBAAA,IAA2B;AAAA,KAC1E,CAAA;AACD,IAAA,OAAO,IAAA,CAAK,YAAA,CAAa,GAAA,CAAI,GAAG,CAAA,CAAG,KAAA;AAAA,EACrC;AAAA,EAEA,WAAW,GAAA,EAAmB;AAC5B,IAAA,IAAA,CAAK,YAAA,CAAa,OAAO,GAAG,CAAA;AAAA,EAC9B;AAAA,EAEQ,SAAA,GAAqB;AAC3B,IAAA,OAAO,CAAC,CAAC,IAAA,CAAK,IAAA,IAAQ,KAAK,GAAA,EAAI,GAAI,KAAK,IAAA,CAAK,SAAA;AAAA,EAC/C;AAAA,EAEA,MAAM,iBAAA,GAA+C;AACnD,IAAA,IAAI,KAAK,SAAA,EAAU,SAAU,IAAA,CAAK,WAAA,CAAY,KAAK,IAAK,CAAA;AACxD,IAAA,IAAI,IAAA,CAAK,QAAA,EAAU,OAAO,IAAA,CAAK,QAAA;AAG/B,IAAA,MAAM,UAAA,GAAa,CAAC,CAAC,IAAA,CAAK,IAAA,EAAM,YAAA;AAChC,IAAA,MAAM,MAAA,GAAS,UAAA,GACX,IAAA,CAAK,cAAA,CAAe,SAAS,CAAA,CAAE,KAAA,CAAM,MAAM,IAAA,CAAK,eAAe,OAAO,CAAC,CAAA,GACvE,IAAA,CAAK,eAAe,OAAO,CAAA;AAC/B,IAAA,MAAM,CAAA,GAAI,MAAA,CAAO,OAAA,CAAQ,MAAM;AAC7B,MAAA,IAAA,CAAK,QAAA,GAAW,MAAA;AAAA,IAClB,CAAC,CAAA;AACD,IAAA,IAAA,CAAK,QAAA,GAAW,CAAA;AAChB,IAAA,OAAO,CAAA;AAAA,EACT;AAAA;AAAA,EAGA,MAAM,gBAAA,GAA8C;AAClD,IAAA,IAAI,CAAC,IAAA,CAAK,IAAA,EAAM,OAAO,KAAK,iBAAA,EAAkB;AAC9C,IAAA,OAAO,IAAA,CAAK,eAAe,SAAS,CAAA;AAAA,EACtC;AAAA,EAEA,mBAAA,GAA4B;AAC1B,IAAA,IAAA,CAAK,IAAA,GAAO,MAAA;AACZ,IAAA,IAAA,CAAK,SAAA,EAAW,MAAM,IAAI,CAAA;AAAA,EAC5B;AAAA,EAEA,oBAAoB,GAAA,EAIX;AACP,IAAA,MAAM,IAAA,GAAO,KAAK,eAAA,IAAmB,IAAA,CAAK,IAAI,WAAA,CAAY,UAAA,EAAY,WAAW,EAAC;AAClF,IAAA,IAAA,CAAK,eAAA,GAAkB,EAAE,GAAG,IAAA,EAAM,GAAG,GAAA,EAAI;AACzC,IAAA,IAAA,CAAK,mBAAA,EAAoB;AAAA,EAC3B;AAAA;AAAA,EAGA,eAAA,GAAwB;AACtB,IAAA,IAAI,IAAA,CAAK,MAAM,IAAA,CAAK,IAAA,GAAO,EAAE,GAAG,IAAA,CAAK,IAAA,EAAM,SAAA,EAAW,CAAA,EAAE;AAAA,EAC1D;AAAA,EAEQ,YAAY,CAAA,EAA+D;AACjF,IAAA,MAAM,EAAE,SAAA,EAAW,UAAA,EAAY,GAAG,MAAK,GAAI,CAAA;AAC3C,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEA,MAAc,eAAe,IAAA,EAAsD;AACjF,IAAA,MAAM,EAAA,GAAK,IAAA,CAAK,GAAA,CAAI,WAAA,CAAY,UAAA;AAChC,IAAA,IAAI,CAAC,IAAI,QAAA,EAAU;AACjB,MAAA,MAAM,IAAI,MAAM,kEAAkE,CAAA;AAAA,IACpF;AACA,IAAA,MAAM,GAAA,GAAM,IAAI,GAAA,CAAI,CAAA,EAAG,KAAK,GAAA,CAAI,IAAI,CAAA,8BAAA,EAAiC,IAAI,CAAA,CAAE,CAAA;AAC3E,IAAA,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,QAAA,EAAU,IAAA,CAAK,IAAI,MAAM,CAAA;AAC9C,IAAA,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,WAAA,EAAa,EAAA,CAAG,QAAQ,CAAA;AAG7C,IAAA,MAAM,CAAA,GAAI,IAAA,CAAK,eAAA,IAAmB,EAAA,CAAG,OAAA;AACrC,IAAA,IAAI,GAAG,QAAA,EAAU,GAAA,CAAI,aAAa,GAAA,CAAI,UAAA,EAAY,EAAE,QAAQ,CAAA;AAC5D,IAAA,IAAI,GAAG,QAAA,EAAU,GAAA,CAAI,aAAa,GAAA,CAAI,UAAA,EAAY,EAAE,QAAQ,CAAA;AAC5D,IAAA,IAAI,GAAG,cAAA,EAAgB,GAAA,CAAI,aAAa,GAAA,CAAI,gBAAA,EAAkB,EAAE,cAAc,CAAA;AAC9E,IAAA,IAAI,IAAA,KAAS,SAAA,IAAa,IAAA,CAAK,IAAA,EAAM;AACnC,MAAA,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,eAAA,EAAiB,IAAA,CAAK,KAAK,YAAY,CAAA;AAAA,IAC9D;AACA,IAAA,IAAI;AACF,MAAA,MAAM,GAAA,GAAM,MAAM,IAAA,CAAK,YAAA,CAAa,KAAK,EAAE,MAAA,EAAQ,OAAO,CAAA;AAC1D,MAAA,MAAM,IAAA,GAAQ,MAAM,GAAA,CAAI,IAAA,GAAO,KAAA,CAAM,OAAO,EAAC,CAAE,CAAA;AAC/C,MAAA,IAAI,CAAC,IAAI,EAAA,EAAI;AACX,QAAA,IAAA,CAAK,aAAA,CAAc,aAAa,KAAK,CAAA;AACrC,QAAA,MAAM,IAAI,gBAAA,CAAiB,CAAA,gBAAA,EAAmB,IAAI,CAAA,OAAA,CAAA,EAAW,GAAA,CAAI,QAAQ,IAAI,CAAA;AAAA,MAC/E;AACA,MAAA,MAAM,UAAA,GAAa,KAAK,GAAA,EAAI;AAC5B,MAAA,IAAA,CAAK,IAAA,GAAO;AAAA,QACV,aAAa,IAAA,CAAK,YAAA;AAAA,QAClB,cAAc,IAAA,CAAK,aAAA;AAAA,QACnB,WAAW,IAAA,CAAK,SAAA;AAAA,QAChB,WAAW,IAAA,CAAK,UAAA;AAAA,QAChB,WACE,UAAA,GAAA,CACE,IAAA,CAAK,aAAwB,IAAA,CAAK,GAAA,CAAI,MAAM,uBAAA,IAA2B;AAAA,OAC7E;AACA,MAAA,IAAA,CAAK,WAAW,KAAA,CAAM;AAAA,QACpB,YAAA,EAAc,KAAK,IAAA,CAAK,YAAA;AAAA,QACxB,SAAA,EAAW,KAAK,IAAA,CAAK;AAAA,OACtB,CAAA;AACD,MAAA,IAAA,CAAK,aAAA,CAAc,aAAa,IAAI,CAAA;AACpC,MAAA,OAAO,IAAA,CAAK,WAAA,CAAY,IAAA,CAAK,IAAI,CAAA;AAAA,IACnC,SAAS,GAAA,EAAK;AAEZ,MAAA,IAAI,EAAE,eAAe,gBAAA,CAAA,EAAmB;AACtC,QAAA,IAAA,CAAK,aAAA,CAAc,aAAa,KAAK,CAAA;AAAA,MACvC;AACA,MAAA,MAAM,GAAA;AAAA,IACR;AAAA,EACF;AACF;;;AChYA,IAAM,IAAA,GAAoB,KAAK,SAAA,EAAU;AAoBlC,IAAM,wBAAN,MAA4B;AAAA,EAEjC,YAA6B,GAAA,EAAoB;AAApB,IAAA,IAAA,CAAA,GAAA,GAAA,GAAA;AAAA,EAAqB;AAAA,EAArB,GAAA;AAAA,EAD7B,OAAgB,OAAA,GAAU,SAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ1B,MAAM,gBAAA,CAAiB,OAAA,GAAuB,IAAA,EAA8B;AAC1E,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,OAAA,CAAuB;AAAA,MAC1C,MAAA,EAAQ,KAAA;AAAA,MACR,IAAA,EAAM,CAAA,iBAAA,EAAoB,IAAA,CAAK,GAAA,CAAI,MAAM,CAAA,sBAAA,CAAA;AAAA,MACzC,IAAA,EAAM;AAAA,KACP,CAAA;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,SAAA,CACJ,KAAA,EACA,OAAA,EACiC;AACjC,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,OAAA,CAAgC;AAAA,MACnD,MAAA,EAAQ,MAAA;AAAA,MACR,IAAA,EAAM,CAAA,iBAAA,EAAoB,IAAA,CAAK,GAAA,CAAI,MAAM,CAAA,2BAAA,CAAA;AAAA,MACzC,IAAA,EAAM,gBAAgB,OAAO,CAAA;AAAA,MAC7B,IAAA,EAAM;AAAA,KACP,CAAA;AAAA,EACH;AACF","file":"chunk-PYLWZZC4.js","sourcesContent":["import type { ResolvedConfig, ServiceCredentials } from \"./config\";\nimport { EmporixAuthError, EmporixTimeoutError, EmporixNetworkError } from \"./errors\";\n\n/** Which token a call should use. */\nexport type AuthKind = \"service\" | \"customer\" | \"anonymous\" | \"raw\";\n\n/** Caller-supplied, per-call auth selector. Never stored on the client. */\nexport type AuthContext =\n | { kind: \"service\"; credentials?: string }\n | { kind: \"anonymous\" }\n | { kind: \"customer\"; token: string }\n | { kind: \"raw\"; token: string };\n\n/**\n * Supplies a fresh customer token when a `customer`-kind request 401s. The host\n * (e.g. EmporixProvider) implements this; the SDK never refreshes the\n * caller-owned customer token unless a refresher is registered.\n */\nexport interface CustomerTokenRefresher {\n /**\n * Called on a `customer`-kind 401. Receives the token that just failed;\n * returns a fresh customer token to retry with, or `null` to give up (the\n * 401 then propagates as EmporixAuthError).\n */\n refresh(expiredToken: string): Promise<string | null>;\n}\n\n/** An obtained anonymous storefront session. */\nexport interface AnonymousSession {\n accessToken: string;\n refreshToken: string;\n sessionId: string;\n expiresIn: number;\n}\n\n/**\n * Persistence callback for anonymous sessions. `read` is called once on the\n * first need for an anonymous token to bootstrap a possibly-existing session;\n * `write` is called after every successful login or refresh. `write(null)`\n * means the SDK is invalidating the stored session.\n */\nexport interface AnonymousSessionStore {\n read(): { refreshToken: string; sessionId: string } | null;\n write(session: { refreshToken: string; sessionId: string } | null): void;\n}\n\n/** Supplies SDK-managed tokens (service/custom + anonymous). May be user-injected. */\nexport interface TokenProvider {\n /** Service/custom client-credentials token for the named credential set. */\n getToken(credentialSet: string): Promise<string>;\n /** Cached anonymous storefront session (preserves sessionId across refreshes). */\n getAnonymousToken(): Promise<AnonymousSession>;\n /** Refresh the anonymous session, preserving sessionId. */\n refreshAnonymous?(): Promise<AnonymousSession>;\n /** Invalidate a cached SDK-managed token so the next call re-auths. */\n invalidate?(credentialSet: string): void;\n /** Invalidate the cached anonymous session entirely (next call re-logs in). */\n invalidateAnonymous?(): void;\n /**\n * Mark the anonymous access token stale but keep the refresh token, so the\n * next {@link getAnonymousToken} refreshes (preserving sessionId) rather\n * than starting a brand-new session.\n */\n expireAnonymous?(): void;\n /**\n * Install a persistence adapter for the anonymous session. The host (e.g.\n * `EmporixProvider`) calls this at construction so the SDK can bootstrap\n * an existing session and persist refreshes. Idempotent: a later call\n * replaces the previous adapter.\n */\n attachAnonymousStore?(store: AnonymousSessionStore): void;\n /**\n * Override the storefront context (currency/site/country) used for the next\n * anonymous login, then invalidate the current anonymous session so it\n * re-mints with the new context. No-op for providers without anon support.\n */\n setAnonymousContext?(ctx: {\n currency?: string;\n siteCode?: string;\n targetLocation?: string;\n }): void;\n /**\n * Subscribe to token-refresh events. Optional — implementations may no-op.\n * Returns an unsubscribe function. `DefaultTokenProvider` emits for the\n * anonymous-login / anonymous-refresh paths.\n */\n onRefresh?(\n listener: (event: { kind: \"anonymous\" | \"customer\"; success: boolean }) => void,\n ): () => void;\n}\n\n/** Tiny constructors for {@link AuthContext}. */\nexport const auth = {\n /** Service/custom credential set (default `backend`). */\n service: (credentials?: string): AuthContext =>\n credentials === undefined ? { kind: \"service\" } : { kind: \"service\", credentials },\n /** Cached anonymous storefront token. */\n anonymous: (): AuthContext => ({ kind: \"anonymous\" }),\n /** Caller-owned customer bearer token. */\n customer: (token: string): AuthContext => ({ kind: \"customer\", token }),\n /** Exact token, no transformation (SSO / token-exchange). */\n raw: (token: string): AuthContext => ({ kind: \"raw\", token }),\n};\n\n/** Resolves an {@link AuthContext} to a concrete bearer token. */\nexport async function resolveToken(ctx: AuthContext, provider: TokenProvider): Promise<string> {\n switch (ctx.kind) {\n case \"service\":\n return provider.getToken(ctx.credentials ?? \"backend\");\n case \"anonymous\":\n return (await provider.getAnonymousToken()).accessToken;\n case \"customer\":\n case \"raw\":\n return ctx.token;\n }\n}\n\n/**\n * Late-bindable, single-flight holder for an optional\n * {@link CustomerTokenRefresher}. Single-flight is required because Emporix\n * rotates the refresh token on each refresh — concurrent refreshes would\n * invalidate each other. Off (returns `null`) until a refresher is set.\n */\nexport class CustomerRefreshRegistry {\n private refresher: CustomerTokenRefresher | null = null;\n private inflight: Promise<string | null> | null = null;\n\n set(refresher: CustomerTokenRefresher | null): void {\n this.refresher = refresher;\n }\n\n get enabled(): boolean {\n return this.refresher !== null;\n }\n\n /** Concurrent callers share one in-flight refresh. */\n refresh(expiredToken: string): Promise<string | null> {\n if (!this.refresher) return Promise.resolve(null);\n if (this.inflight) return this.inflight;\n const p = Promise.resolve(this.refresher.refresh(expiredToken)).finally(() => {\n this.inflight = null;\n });\n this.inflight = p;\n return p;\n }\n}\n\ninterface CacheEntry {\n token: string;\n expiresAt: number;\n obtainedAt: number;\n}\n\n/** SDK-owned token provider: client-credentials service tokens + anonymous session. */\nexport class DefaultTokenProvider implements TokenProvider {\n private readonly serviceCache = new Map<string, CacheEntry>();\n private readonly serviceLocks = new Map<string, Promise<string>>();\n private anon: (AnonymousSession & { expiresAt: number }) | undefined;\n private anonLock: Promise<AnonymousSession> | undefined;\n private anonStore?: AnonymousSessionStore;\n private contextOverride:\n | { currency?: string; siteCode?: string; targetLocation?: string }\n | undefined;\n private readonly refreshListeners = new Set<\n (event: { kind: \"anonymous\" | \"customer\"; success: boolean }) => void\n >();\n\n constructor(private readonly cfg: ResolvedConfig) {}\n\n onRefresh(\n listener: (event: { kind: \"anonymous\" | \"customer\"; success: boolean }) => void,\n ): () => void {\n this.refreshListeners.add(listener);\n return () => {\n this.refreshListeners.delete(listener);\n };\n }\n\n private notifyRefresh(kind: \"anonymous\" | \"customer\", success: boolean): void {\n for (const l of this.refreshListeners) {\n try {\n l({ kind, success });\n } catch {\n // Never let a telemetry listener break the auth path.\n }\n }\n }\n\n attachAnonymousStore(store: AnonymousSessionStore): void {\n this.anonStore = store;\n // Bootstrap `this.anon` from the store if we don't have it yet. The seeded\n // session has expiresAt = 0 so the next getAnonymousToken triggers a refresh\n // (which preserves sessionId) instead of a fresh login.\n if (!this.anon) {\n const persisted = store.read();\n if (persisted) {\n this.anon = {\n accessToken: \"\",\n refreshToken: persisted.refreshToken,\n sessionId: persisted.sessionId,\n expiresIn: 0,\n expiresAt: 0,\n };\n }\n }\n }\n\n private creds(set: string): ServiceCredentials {\n if (set === \"backend\") {\n const b = this.cfg.credentials.backend;\n if (!b?.clientId || !b.secret) {\n throw new EmporixAuthError(\n \"A 'service' AuthContext was used but credentials.backend is not configured\",\n );\n }\n return b;\n }\n const c = this.cfg.credentials.custom?.[set];\n if (!c) throw new Error(`Unknown credential set \"${set}\"`);\n return c;\n }\n\n private fresh(e: CacheEntry | undefined): boolean {\n if (!e) return false;\n const now = Date.now();\n if (now - e.obtainedAt >= this.cfg.cache.maxLifetimeSeconds * 1000) return false;\n return now < e.expiresAt;\n }\n\n async getToken(set: string): Promise<string> {\n // Surface an unknown credential set before entering the cache/lock path.\n this.creds(set);\n const cached = this.serviceCache.get(set);\n if (this.fresh(cached)) return cached!.token;\n const inflight = this.serviceLocks.get(set);\n if (inflight) return inflight;\n const p = this.requestServiceToken(set).finally(() => this.serviceLocks.delete(set));\n this.serviceLocks.set(set, p);\n return p;\n }\n\n /**\n * fetch with the configured read timeout. Token endpoints sit in front of\n * single-flight locks — one hung call would otherwise block every request\n * on this credential set forever.\n */\n private async boundedFetch(url: string | URL, init: RequestInit = {}): Promise<Response> {\n const ms = this.cfg.timeouts?.readMs ?? 60_000;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), ms);\n try {\n return await fetch(url, { ...init, signal: controller.signal });\n } catch (err) {\n if ((err as Error).name === \"AbortError\") {\n throw new EmporixTimeoutError(`token request timed out after ${ms}ms`);\n }\n throw new EmporixNetworkError(`token request network failure: ${(err as Error).message}`);\n } finally {\n clearTimeout(timer);\n }\n }\n\n private async requestServiceToken(set: string): Promise<string> {\n const c = this.creds(set);\n const body = new URLSearchParams({\n grant_type: \"client_credentials\",\n client_id: c.clientId,\n client_secret: c.secret,\n });\n if (c.scope) body.set(\"scope\", c.scope);\n const res = await this.boundedFetch(`${this.cfg.host}/oauth/token`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body,\n });\n const json = (await res.json().catch(() => ({}))) as Record<string, unknown>;\n if (!res.ok) {\n throw new EmporixAuthError(`Token request failed for \"${set}\"`, res.status, json);\n }\n const obtainedAt = Date.now();\n const ttl = Number((json.expires_in as number | undefined) ?? 3600);\n this.serviceCache.set(set, {\n token: json.access_token as string,\n obtainedAt,\n expiresAt: obtainedAt + (ttl - this.cfg.cache.expirationBufferSeconds) * 1000,\n });\n return this.serviceCache.get(set)!.token;\n }\n\n invalidate(set: string): void {\n this.serviceCache.delete(set);\n }\n\n private anonFresh(): boolean {\n return !!this.anon && Date.now() < this.anon.expiresAt;\n }\n\n async getAnonymousToken(): Promise<AnonymousSession> {\n if (this.anonFresh()) return this.stripExpiry(this.anon!);\n if (this.anonLock) return this.anonLock;\n // Token expired but a refresh token survives → refresh to keep the same\n // sessionId; only fall back to a brand-new login if the refresh fails.\n const canRefresh = !!this.anon?.refreshToken;\n const obtain = canRefresh\n ? this.fetchAnonymous(\"refresh\").catch(() => this.fetchAnonymous(\"login\"))\n : this.fetchAnonymous(\"login\");\n const p = obtain.finally(() => {\n this.anonLock = undefined;\n });\n this.anonLock = p;\n return p;\n }\n\n /** Refreshes the anonymous session, preserving its sessionId. */\n async refreshAnonymous(): Promise<AnonymousSession> {\n if (!this.anon) return this.getAnonymousToken();\n return this.fetchAnonymous(\"refresh\");\n }\n\n invalidateAnonymous(): void {\n this.anon = undefined;\n this.anonStore?.write(null);\n }\n\n setAnonymousContext(ctx: {\n currency?: string;\n siteCode?: string;\n targetLocation?: string;\n }): void {\n const base = this.contextOverride ?? this.cfg.credentials.storefront?.context ?? {};\n this.contextOverride = { ...base, ...ctx };\n this.invalidateAnonymous();\n }\n\n /** Force a stale access token while keeping the refresh token + sessionId. */\n expireAnonymous(): void {\n if (this.anon) this.anon = { ...this.anon, expiresAt: 0 };\n }\n\n private stripExpiry(s: AnonymousSession & { expiresAt: number }): AnonymousSession {\n const { expiresAt: _expiresAt, ...rest } = s;\n return rest;\n }\n\n private async fetchAnonymous(mode: \"login\" | \"refresh\"): Promise<AnonymousSession> {\n const sf = this.cfg.credentials.storefront;\n if (!sf?.clientId) {\n throw new Error(\"credentials.storefront.clientId is required for anonymous tokens\");\n }\n const url = new URL(`${this.cfg.host}/customerlogin/auth/anonymous/${mode}`);\n url.searchParams.set(\"tenant\", this.cfg.tenant);\n url.searchParams.set(\"client_id\", sf.clientId);\n // Context is the runtime override (set via setAnonymousContext) if present,\n // else the config-fixed context.\n const c = this.contextOverride ?? sf.context;\n if (c?.currency) url.searchParams.set(\"currency\", c.currency);\n if (c?.siteCode) url.searchParams.set(\"siteCode\", c.siteCode);\n if (c?.targetLocation) url.searchParams.set(\"targetLocation\", c.targetLocation);\n if (mode === \"refresh\" && this.anon) {\n url.searchParams.set(\"refresh_token\", this.anon.refreshToken);\n }\n try {\n const res = await this.boundedFetch(url, { method: \"GET\" });\n const json = (await res.json().catch(() => ({}))) as Record<string, unknown>;\n if (!res.ok) {\n this.notifyRefresh(\"anonymous\", false);\n throw new EmporixAuthError(`Anonymous token ${mode} failed`, res.status, json);\n }\n const obtainedAt = Date.now();\n this.anon = {\n accessToken: json.access_token as string,\n refreshToken: json.refresh_token as string,\n sessionId: json.sessionId as string,\n expiresIn: json.expires_in as number,\n expiresAt:\n obtainedAt +\n ((json.expires_in as number) - this.cfg.cache.expirationBufferSeconds) * 1000,\n };\n this.anonStore?.write({\n refreshToken: this.anon.refreshToken,\n sessionId: this.anon.sessionId,\n });\n this.notifyRefresh(\"anonymous\", true);\n return this.stripExpiry(this.anon);\n } catch (err) {\n // Re-throw EmporixAuthError as-is; for non-Emporix errors (network), also notify.\n if (!(err instanceof EmporixAuthError)) {\n this.notifyRefresh(\"anonymous\", false);\n }\n throw err;\n }\n }\n}\n// Customer-token refreshes happen via client.customers.refresh() and don't\n// route through this TokenProvider — only the anonymous flow notifies.\n// React-side useCustomerSession.refresh emits its own telemetry event in a\n// follow-up if needed.\n","import type { ClientContext } from \"../core/context\";\nimport { auth, type AuthContext } from \"../core/auth\";\nimport { requireCustomer } from \"../core/require-customer\";\nimport type {\n PaymentModeFrontendResponse,\n AuthorizePaymentRequest,\n} from \"../generated/payment\";\n\nconst ANON: AuthContext = auth.anonymous();\n\n/** A frontend payment mode (generated). */\nexport type PaymentMode = PaymentModeFrontendResponse;\n\n/** Post-checkout authorize request (generated; caller sends the exact wire shape). */\nexport type AuthorizePaymentInput = AuthorizePaymentRequest;\n\n/** Post-checkout authorize result. */\nexport interface AuthorizePaymentResult {\n successful: boolean;\n paymentTransactionId?: string;\n authorizationToken?: string;\n requiresExternalPayment?: boolean;\n externalPaymentRedirectURL?: string;\n externalPaymentHttpMethod?: string;\n [k: string]: unknown;\n}\n\n/** Payment-Gateway: list frontend payment modes, authorize deferred payments. */\nexport class PaymentGatewayService {\n static readonly channel = \"payment\" as const;\n constructor(private readonly ctx: ClientContext) {}\n\n /**\n * Lists configured frontend payment modes. The endpoint requires a bearer\n * token but no customer scope (\"No scope required\"), so it defaults to an\n * anonymous context and works for guests and logged-in customers alike.\n */\n async listPaymentModes(authCtx: AuthContext = ANON): Promise<PaymentMode[]> {\n return this.ctx.http.request<PaymentMode[]>({\n method: \"GET\",\n path: `/payment-gateway/${this.ctx.tenant}/paymentmodes/frontend`,\n auth: authCtx,\n });\n }\n\n /** Authorizes a post-checkout (deferred) payment for an existing order. */\n async authorize(\n input: AuthorizePaymentInput,\n authCtx?: AuthContext,\n ): Promise<AuthorizePaymentResult> {\n return this.ctx.http.request<AuthorizePaymentResult>({\n method: \"POST\",\n path: `/payment-gateway/${this.ctx.tenant}/payment/frontend/authorize`,\n auth: requireCustomer(authCtx),\n body: input,\n });\n }\n}\n"]}
package/dist/index.cjs CHANGED
@@ -783,7 +783,7 @@ var HttpClient = class {
783
783
  };
784
784
 
785
785
  // package.json
786
- var version = "2.15.0";
786
+ var version = "2.16.0";
787
787
 
788
788
  // src/version.ts
789
789
  var SDK_VERSION = version;
@@ -1585,26 +1585,31 @@ var CheckoutService = class {
1585
1585
  };
1586
1586
 
1587
1587
  // src/services/payment.ts
1588
+ var ANON3 = auth.anonymous();
1588
1589
  var PaymentGatewayService = class {
1589
1590
  constructor(ctx) {
1590
1591
  this.ctx = ctx;
1591
1592
  }
1592
1593
  ctx;
1593
1594
  static channel = "payment";
1594
- /** Lists configured frontend payment modes. */
1595
- async listPaymentModes(auth2) {
1595
+ /**
1596
+ * Lists configured frontend payment modes. The endpoint requires a bearer
1597
+ * token but no customer scope ("No scope required"), so it defaults to an
1598
+ * anonymous context and works for guests and logged-in customers alike.
1599
+ */
1600
+ async listPaymentModes(authCtx = ANON3) {
1596
1601
  return this.ctx.http.request({
1597
1602
  method: "GET",
1598
1603
  path: `/payment-gateway/${this.ctx.tenant}/paymentmodes/frontend`,
1599
- auth: requireCustomer(auth2)
1604
+ auth: authCtx
1600
1605
  });
1601
1606
  }
1602
1607
  /** Authorizes a post-checkout (deferred) payment for an existing order. */
1603
- async authorize(input, auth2) {
1608
+ async authorize(input, authCtx) {
1604
1609
  return this.ctx.http.request({
1605
1610
  method: "POST",
1606
1611
  path: `/payment-gateway/${this.ctx.tenant}/payment/frontend/authorize`,
1607
- auth: requireCustomer(auth2),
1612
+ auth: requireCustomer(authCtx),
1608
1613
  body: input
1609
1614
  });
1610
1615
  }
@@ -1621,10 +1626,10 @@ function normalizeMatch(raw) {
1621
1626
  };
1622
1627
  return { ...base, itemId, itemRef };
1623
1628
  }
1624
- var ANON3 = { kind: "anonymous" };
1629
+ var ANON4 = { kind: "anonymous" };
1625
1630
  var SERVICE = { kind: "service" };
1626
1631
  function requireContextAuth(auth2) {
1627
- const a = auth2 ?? ANON3;
1632
+ const a = auth2 ?? ANON4;
1628
1633
  if (a.kind === "anonymous" || a.kind === "customer" || a.kind === "raw") return a;
1629
1634
  throw new EmporixAuthError(
1630
1635
  "match-prices-by-context requires an anonymous, customer, or raw AuthContext"
@@ -2096,7 +2101,7 @@ var SegmentService = class {
2096
2101
  };
2097
2102
 
2098
2103
  // src/services/site.ts
2099
- var ANON4 = auth.anonymous();
2104
+ var ANON5 = auth.anonymous();
2100
2105
  var SiteService = class {
2101
2106
  constructor(ctx) {
2102
2107
  this.ctx = ctx;
@@ -2104,7 +2109,7 @@ var SiteService = class {
2104
2109
  ctx;
2105
2110
  static channel = "site";
2106
2111
  /** Lists active sites. */
2107
- async list(authCtx = ANON4) {
2112
+ async list(authCtx = ANON5) {
2108
2113
  return this.ctx.http.request({
2109
2114
  method: "GET",
2110
2115
  path: `/site/${this.ctx.tenant}/sites`,
@@ -2112,7 +2117,7 @@ var SiteService = class {
2112
2117
  });
2113
2118
  }
2114
2119
  /** Retrieves one site by code. */
2115
- async get(code, authCtx = ANON4) {
2120
+ async get(code, authCtx = ANON5) {
2116
2121
  return this.ctx.http.request({
2117
2122
  method: "GET",
2118
2123
  path: `/site/${this.ctx.tenant}/sites/${code}`,
@@ -2123,7 +2128,7 @@ var SiteService = class {
2123
2128
  * Returns the tenant's default site (the one with `default: true`).
2124
2129
  * Throws if no default is configured — a tenant should always have one.
2125
2130
  */
2126
- async current(authCtx = ANON4) {
2131
+ async current(authCtx = ANON5) {
2127
2132
  const sites = await this.list(authCtx);
2128
2133
  const def = sites.find((s) => s.default);
2129
2134
  if (!def) {
@@ -2136,7 +2141,7 @@ var SiteService = class {
2136
2141
  };
2137
2142
 
2138
2143
  // src/services/session-context.ts
2139
- var ANON5 = auth.anonymous();
2144
+ var ANON6 = auth.anonymous();
2140
2145
  var SessionContextService = class {
2141
2146
  constructor(ctx) {
2142
2147
  this.ctx = ctx;
@@ -2148,7 +2153,7 @@ var SessionContextService = class {
2148
2153
  * 404 (no session exists yet — the session-context is created server-side
2149
2154
  * only after the user creates a cart).
2150
2155
  */
2151
- async get(authCtx = ANON5) {
2156
+ async get(authCtx = ANON6) {
2152
2157
  try {
2153
2158
  return await this.ctx.http.request({
2154
2159
  method: "GET",
@@ -2166,7 +2171,7 @@ var SessionContextService = class {
2166
2171
  * server returned 404 on the version-lookup GET, so there is nothing to
2167
2172
  * update). Non-404 errors propagate.
2168
2173
  */
2169
- async patch(input, authCtx = ANON5) {
2174
+ async patch(input, authCtx = ANON6) {
2170
2175
  let version2 = input.version;
2171
2176
  if (version2 === void 0) {
2172
2177
  const current = await this.get(authCtx);
@@ -2501,7 +2506,7 @@ var SalesOrdersService = class {
2501
2506
  };
2502
2507
 
2503
2508
  // src/services/availability.ts
2504
- var ANON6 = { kind: "anonymous" };
2509
+ var ANON7 = { kind: "anonymous" };
2505
2510
  var AvailabilityService = class {
2506
2511
  constructor(ctx) {
2507
2512
  this.ctx = ctx;
@@ -2512,7 +2517,7 @@ var AvailabilityService = class {
2512
2517
  * Single product. Resolves the availability record, or — when
2513
2518
  * `opts.defaultAvailableOnNotFound` is set — a default available record on 404.
2514
2519
  */
2515
- async get(productId, siteCode, auth2 = ANON6, opts = {}) {
2520
+ async get(productId, siteCode, auth2 = ANON7, opts = {}) {
2516
2521
  try {
2517
2522
  return await this.ctx.http.request({
2518
2523
  method: "GET",
@@ -2535,7 +2540,7 @@ var AvailabilityService = class {
2535
2540
  * `opts.defaultAvailableOnNotFound` is set). The result preserves input order
2536
2541
  * and length. An empty `productIds` resolves to `[]` without a request.
2537
2542
  */
2538
- async getMany(productIds, siteCode, auth2 = ANON6, opts = {}) {
2543
+ async getMany(productIds, siteCode, auth2 = ANON7, opts = {}) {
2539
2544
  if (productIds.length === 0) return [];
2540
2545
  const found = await this.ctx.http.request({
2541
2546
  method: "POST",