@commercengine/storefront-sdk 0.8.2 → 0.9.1
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/dist/index.d.ts +5618 -5161
- package/dist/index.iife.js +886 -768
- package/dist/index.iife.js.map +1 -1
- package/dist/index.js +794 -676
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1,648 +1,825 @@
|
|
|
1
1
|
import createClient from "openapi-fetch";
|
|
2
2
|
import { decodeJwt } from "jose";
|
|
3
3
|
|
|
4
|
-
//#region
|
|
4
|
+
//#region ../sdk-core/dist/index.js
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* @param token - The JWT token to decode
|
|
9
|
-
* @returns User information or null if token is invalid
|
|
6
|
+
* Response utilities for debugging and working with Response objects
|
|
10
7
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
var ResponseUtils = class {
|
|
9
|
+
/**
|
|
10
|
+
* Get response headers as a plain object
|
|
11
|
+
*/
|
|
12
|
+
static getHeaders(response) {
|
|
13
|
+
return Object.fromEntries(response.headers.entries());
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Get specific header value
|
|
17
|
+
*/
|
|
18
|
+
static getHeader(response, name) {
|
|
19
|
+
return response.headers.get(name);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Check if response was successful
|
|
23
|
+
*/
|
|
24
|
+
static isSuccess(response) {
|
|
25
|
+
return response.ok;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Get response metadata
|
|
29
|
+
*/
|
|
30
|
+
static getMetadata(response) {
|
|
14
31
|
return {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
isLoggedIn: payload.is_logged_in,
|
|
23
|
-
isAnonymous: !payload.is_logged_in,
|
|
24
|
-
customerId: payload.customer_id,
|
|
25
|
-
customerGroupId: payload.customer_group_id,
|
|
26
|
-
anonymousId: payload.anonymous_id,
|
|
27
|
-
tokenExpiry: /* @__PURE__ */ new Date(payload.exp * 1e3),
|
|
28
|
-
tokenIssuedAt: /* @__PURE__ */ new Date(payload.iat * 1e3)
|
|
32
|
+
status: response.status,
|
|
33
|
+
statusText: response.statusText,
|
|
34
|
+
ok: response.ok,
|
|
35
|
+
url: response.url,
|
|
36
|
+
redirected: response.redirected,
|
|
37
|
+
type: response.type,
|
|
38
|
+
headers: Object.fromEntries(response.headers.entries())
|
|
29
39
|
};
|
|
30
|
-
} catch (error) {
|
|
31
|
-
console.warn("Failed to decode JWT token:", error);
|
|
32
|
-
return null;
|
|
33
40
|
}
|
|
34
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Clone and read response as text (useful for debugging)
|
|
43
|
+
* Note: This can only be called once per response
|
|
44
|
+
*/
|
|
45
|
+
static async getText(response) {
|
|
46
|
+
const cloned = response.clone();
|
|
47
|
+
return await cloned.text();
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Clone and read response as JSON (useful for debugging)
|
|
51
|
+
* Note: This can only be called once per response
|
|
52
|
+
*/
|
|
53
|
+
static async getJSON(response) {
|
|
54
|
+
const cloned = response.clone();
|
|
55
|
+
return await cloned.json();
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Format response information for debugging
|
|
59
|
+
*/
|
|
60
|
+
static format(response) {
|
|
61
|
+
const metadata = this.getMetadata(response);
|
|
62
|
+
return `${metadata.status} ${metadata.statusText} - ${metadata.url}`;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Format response for logging purposes (enhanced version)
|
|
66
|
+
*/
|
|
67
|
+
static formatResponse(response) {
|
|
68
|
+
return {
|
|
69
|
+
status: response.status,
|
|
70
|
+
statusText: response.statusText,
|
|
71
|
+
url: response.url,
|
|
72
|
+
ok: response.ok
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
};
|
|
35
76
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* @param token - The JWT token to check
|
|
39
|
-
* @param bufferSeconds - Buffer time in seconds (default: 30)
|
|
40
|
-
* @returns True if token is expired or will expire within buffer time
|
|
77
|
+
* Debug logging utilities
|
|
41
78
|
*/
|
|
42
|
-
|
|
79
|
+
var DebugLogger = class {
|
|
80
|
+
logger;
|
|
81
|
+
responseTextCache = /* @__PURE__ */ new Map();
|
|
82
|
+
constructor(logger) {
|
|
83
|
+
this.logger = logger || ((level, message, data) => {
|
|
84
|
+
console.log(`[${level.toUpperCase()}]`, message);
|
|
85
|
+
if (data) console.log(data);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Log debug information about API request
|
|
90
|
+
*/
|
|
91
|
+
logRequest(request, requestBody) {
|
|
92
|
+
this.logger("info", "API Request Debug Info", {
|
|
93
|
+
method: request.method,
|
|
94
|
+
url: request.url,
|
|
95
|
+
headers: Object.fromEntries(request.headers.entries()),
|
|
96
|
+
body: requestBody,
|
|
97
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Log debug information about API response
|
|
102
|
+
*/
|
|
103
|
+
async logResponse(response, responseBody) {
|
|
104
|
+
if (responseBody && typeof responseBody === "string") this.responseTextCache.set(response.url, responseBody);
|
|
105
|
+
this.logger("info", "API Response Debug Info", {
|
|
106
|
+
url: response.url,
|
|
107
|
+
status: response.status,
|
|
108
|
+
statusText: response.statusText,
|
|
109
|
+
ok: response.ok,
|
|
110
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
111
|
+
redirected: response.redirected,
|
|
112
|
+
type: response.type,
|
|
113
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
114
|
+
});
|
|
115
|
+
if (responseBody) this.logger("info", "API Response Data", {
|
|
116
|
+
data: responseBody,
|
|
117
|
+
contentType: response.headers.get("content-type"),
|
|
118
|
+
contentLength: response.headers.get("content-length")
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Log error information
|
|
123
|
+
*/
|
|
124
|
+
logError(message, error) {
|
|
125
|
+
this.logger("error", message, error);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Get cached response text for a URL (if available)
|
|
129
|
+
*/
|
|
130
|
+
getCachedResponseText(url) {
|
|
131
|
+
return this.responseTextCache.get(url) || null;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Clear cached response texts
|
|
135
|
+
*/
|
|
136
|
+
clearCache() {
|
|
137
|
+
this.responseTextCache.clear();
|
|
138
|
+
}
|
|
139
|
+
info(message, data) {
|
|
140
|
+
this.logger("info", message, data);
|
|
141
|
+
}
|
|
142
|
+
warn(message, data) {
|
|
143
|
+
this.logger("warn", message, data);
|
|
144
|
+
}
|
|
145
|
+
error(message, data) {
|
|
146
|
+
this.logger("error", message, data);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* Extract request body for logging
|
|
151
|
+
*/
|
|
152
|
+
async function extractRequestBody(request) {
|
|
153
|
+
if (request.method === "GET" || request.method === "HEAD") return null;
|
|
43
154
|
try {
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
155
|
+
const clonedRequest = request.clone();
|
|
156
|
+
const contentType = request.headers.get("content-type")?.toLowerCase();
|
|
157
|
+
if (contentType?.startsWith("application/json")) return await clonedRequest.json();
|
|
158
|
+
else if (contentType?.startsWith("multipart/form-data")) return "[FormData - cannot display]";
|
|
159
|
+
else if (contentType?.startsWith("text/")) return await clonedRequest.text();
|
|
160
|
+
return "[Request body - unknown format]";
|
|
49
161
|
} catch (error) {
|
|
50
|
-
|
|
51
|
-
return true;
|
|
162
|
+
return "[Request body unavailable]";
|
|
52
163
|
}
|
|
53
164
|
}
|
|
54
165
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* @param token - The JWT token
|
|
58
|
-
* @returns User ID (ulid) or null if token is invalid
|
|
166
|
+
* Create debug middleware for openapi-fetch (internal use)
|
|
167
|
+
* Enhanced version that combines original functionality with duration tracking
|
|
59
168
|
*/
|
|
60
|
-
function
|
|
61
|
-
const
|
|
62
|
-
return
|
|
169
|
+
function createDebugMiddleware(logger) {
|
|
170
|
+
const debugLogger = new DebugLogger(logger);
|
|
171
|
+
return {
|
|
172
|
+
async onRequest({ request }) {
|
|
173
|
+
request.__debugStartTime = Date.now();
|
|
174
|
+
const requestBody = await extractRequestBody(request);
|
|
175
|
+
debugLogger.logRequest(request, requestBody);
|
|
176
|
+
return request;
|
|
177
|
+
},
|
|
178
|
+
async onResponse({ request, response }) {
|
|
179
|
+
const startTime = request.__debugStartTime;
|
|
180
|
+
const duration = startTime ? Date.now() - startTime : 0;
|
|
181
|
+
const cloned = response.clone();
|
|
182
|
+
let responseBody = null;
|
|
183
|
+
try {
|
|
184
|
+
const contentType = response.headers.get("content-type")?.toLowerCase();
|
|
185
|
+
if (contentType?.startsWith("application/json")) responseBody = await cloned.json();
|
|
186
|
+
else if (contentType?.startsWith("text/")) responseBody = await cloned.text();
|
|
187
|
+
} catch (error) {}
|
|
188
|
+
await debugLogger.logResponse(response, responseBody);
|
|
189
|
+
if (duration > 0) debugLogger.info(`Request completed in ${duration}ms`, {
|
|
190
|
+
url: request.url,
|
|
191
|
+
method: request.method
|
|
192
|
+
});
|
|
193
|
+
return response;
|
|
194
|
+
},
|
|
195
|
+
async onError({ error, request }) {
|
|
196
|
+
debugLogger.logError("API Request Failed", {
|
|
197
|
+
error: {
|
|
198
|
+
name: error instanceof Error ? error.name : "Unknown",
|
|
199
|
+
message: error instanceof Error ? error.message : String(error),
|
|
200
|
+
stack: error instanceof Error ? error.stack : void 0
|
|
201
|
+
},
|
|
202
|
+
request: {
|
|
203
|
+
method: request.method,
|
|
204
|
+
url: request.url,
|
|
205
|
+
headers: Object.fromEntries(request.headers.entries())
|
|
206
|
+
},
|
|
207
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
208
|
+
});
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
};
|
|
63
212
|
}
|
|
64
213
|
/**
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* @param token - The JWT token
|
|
68
|
-
* @returns True if user is logged in, false otherwise
|
|
214
|
+
* Timeout middleware for Commerce Engine SDKs
|
|
69
215
|
*/
|
|
70
|
-
function isUserLoggedIn(token) {
|
|
71
|
-
const userInfo = extractUserInfoFromToken(token);
|
|
72
|
-
return userInfo?.isLoggedIn || false;
|
|
73
|
-
}
|
|
74
216
|
/**
|
|
75
|
-
*
|
|
217
|
+
* Create timeout middleware for openapi-fetch
|
|
218
|
+
* Adds configurable request timeout functionality
|
|
76
219
|
*
|
|
77
|
-
* @param
|
|
78
|
-
* @returns
|
|
220
|
+
* @param timeoutMs - Timeout duration in milliseconds
|
|
221
|
+
* @returns Middleware object with onRequest handler
|
|
79
222
|
*/
|
|
80
|
-
function
|
|
81
|
-
|
|
82
|
-
|
|
223
|
+
function createTimeoutMiddleware(timeoutMs) {
|
|
224
|
+
return { onRequest: async ({ request }) => {
|
|
225
|
+
const controller = new AbortController();
|
|
226
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
227
|
+
if (request.signal) request.signal.addEventListener("abort", () => controller.abort());
|
|
228
|
+
const newRequest = new Request(request, { signal: controller.signal });
|
|
229
|
+
controller.signal.addEventListener("abort", () => clearTimeout(timeoutId));
|
|
230
|
+
return newRequest;
|
|
231
|
+
} };
|
|
83
232
|
}
|
|
84
|
-
|
|
85
|
-
//#endregion
|
|
86
|
-
//#region src/lib/auth-utils.ts
|
|
87
233
|
/**
|
|
88
|
-
*
|
|
234
|
+
* Transform headers using a transformation mapping
|
|
235
|
+
* Headers not in the transformation map are passed through unchanged
|
|
236
|
+
*
|
|
237
|
+
* @param headers - Headers object with original names
|
|
238
|
+
* @param transformations - Mapping of original names to transformed names
|
|
239
|
+
* @returns Headers object with transformed names
|
|
89
240
|
*/
|
|
90
|
-
function
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
return url.split("?")[0];
|
|
241
|
+
function transformHeaders(headers, transformations) {
|
|
242
|
+
const transformed = {};
|
|
243
|
+
for (const [key, value] of Object.entries(headers)) if (value !== void 0) {
|
|
244
|
+
const headerName = transformations[key] || key;
|
|
245
|
+
transformed[headerName] = value;
|
|
96
246
|
}
|
|
247
|
+
return transformed;
|
|
97
248
|
}
|
|
98
249
|
/**
|
|
99
|
-
*
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
*
|
|
250
|
+
* Merge headers with transformation support
|
|
251
|
+
* Transforms default headers, then merges with method headers
|
|
252
|
+
*
|
|
253
|
+
* @param defaultHeaders - Default headers from SDK configuration
|
|
254
|
+
* @param methodHeaders - Headers passed to the specific method call
|
|
255
|
+
* @param transformations - Mapping for header name transformations
|
|
256
|
+
* @returns Merged headers object with transformations applied
|
|
106
257
|
*/
|
|
107
|
-
function
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
258
|
+
function mergeAndTransformHeaders(defaultHeaders, methodHeaders, transformations) {
|
|
259
|
+
const merged = {};
|
|
260
|
+
if (defaultHeaders && transformations) {
|
|
261
|
+
const transformedDefaults = transformHeaders(defaultHeaders, transformations);
|
|
262
|
+
Object.assign(merged, transformedDefaults);
|
|
263
|
+
} else if (defaultHeaders) Object.assign(merged, defaultHeaders);
|
|
264
|
+
if (methodHeaders) Object.assign(merged, methodHeaders);
|
|
265
|
+
Object.keys(merged).forEach((key) => {
|
|
266
|
+
if (merged[key] === void 0) delete merged[key];
|
|
267
|
+
});
|
|
268
|
+
return merged;
|
|
116
269
|
}
|
|
117
270
|
/**
|
|
118
|
-
*
|
|
271
|
+
* Execute a request and handle the response consistently
|
|
272
|
+
* This provides unified error handling and response processing across all SDKs
|
|
273
|
+
*
|
|
274
|
+
* @param apiCall - Function that executes the API request
|
|
275
|
+
* @returns Promise with the API response in standardized format
|
|
119
276
|
*/
|
|
120
|
-
function
|
|
121
|
-
|
|
277
|
+
async function executeRequest(apiCall) {
|
|
278
|
+
try {
|
|
279
|
+
const { data, error, response } = await apiCall();
|
|
280
|
+
if (error) return {
|
|
281
|
+
data: null,
|
|
282
|
+
error,
|
|
283
|
+
response
|
|
284
|
+
};
|
|
285
|
+
if (data && data.content !== void 0) return {
|
|
286
|
+
data: data.content,
|
|
287
|
+
error: null,
|
|
288
|
+
response
|
|
289
|
+
};
|
|
290
|
+
return {
|
|
291
|
+
data,
|
|
292
|
+
error: null,
|
|
293
|
+
response
|
|
294
|
+
};
|
|
295
|
+
} catch (err) {
|
|
296
|
+
const mockResponse = new Response(null, {
|
|
297
|
+
status: 0,
|
|
298
|
+
statusText: "Network Error"
|
|
299
|
+
});
|
|
300
|
+
const errorResult = {
|
|
301
|
+
data: null,
|
|
302
|
+
error: {
|
|
303
|
+
success: false,
|
|
304
|
+
code: "NETWORK_ERROR",
|
|
305
|
+
message: "Network error occurred",
|
|
306
|
+
error: err
|
|
307
|
+
},
|
|
308
|
+
response: mockResponse
|
|
309
|
+
};
|
|
310
|
+
return errorResult;
|
|
311
|
+
}
|
|
122
312
|
}
|
|
123
|
-
|
|
124
|
-
//#endregion
|
|
125
|
-
//#region src/lib/middleware.ts
|
|
126
313
|
/**
|
|
127
|
-
*
|
|
314
|
+
* Generic base API client that all Commerce Engine SDKs can extend
|
|
315
|
+
* Handles common functionality like middleware setup, request execution, and header management
|
|
316
|
+
* Does NOT include token management - that's SDK-specific
|
|
317
|
+
*
|
|
318
|
+
* @template TPaths - OpenAPI paths type
|
|
319
|
+
* @template THeaders - Supported default headers type
|
|
128
320
|
*/
|
|
129
|
-
var
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
321
|
+
var BaseAPIClient = class {
|
|
322
|
+
client;
|
|
323
|
+
config;
|
|
324
|
+
baseUrl;
|
|
325
|
+
headerTransformations;
|
|
326
|
+
/**
|
|
327
|
+
* Create a new BaseAPIClient
|
|
328
|
+
*
|
|
329
|
+
* @param config - Configuration for the API client
|
|
330
|
+
* @param baseUrl - The base URL for the API (must be provided by subclass)
|
|
331
|
+
* @param headerTransformations - Header name transformations for this SDK
|
|
332
|
+
*/
|
|
333
|
+
constructor(config, baseUrl, headerTransformations = {}) {
|
|
334
|
+
this.config = { ...config };
|
|
335
|
+
this.headerTransformations = headerTransformations;
|
|
336
|
+
this.baseUrl = baseUrl;
|
|
337
|
+
this.client = createClient({ baseUrl: this.baseUrl });
|
|
338
|
+
this.setupMiddleware();
|
|
134
339
|
}
|
|
135
|
-
|
|
136
|
-
|
|
340
|
+
/**
|
|
341
|
+
* Set up all middleware for the client
|
|
342
|
+
*/
|
|
343
|
+
setupMiddleware() {
|
|
344
|
+
if (this.config.timeout) {
|
|
345
|
+
const timeoutMiddleware = createTimeoutMiddleware(this.config.timeout);
|
|
346
|
+
this.client.use(timeoutMiddleware);
|
|
347
|
+
}
|
|
348
|
+
if (this.config.debug) {
|
|
349
|
+
const debugMiddleware = createDebugMiddleware(this.config.logger);
|
|
350
|
+
this.client.use(debugMiddleware);
|
|
351
|
+
}
|
|
137
352
|
}
|
|
138
|
-
|
|
139
|
-
|
|
353
|
+
/**
|
|
354
|
+
* Get the base URL of the API
|
|
355
|
+
*
|
|
356
|
+
* @returns The base URL of the API
|
|
357
|
+
*/
|
|
358
|
+
getBaseUrl() {
|
|
359
|
+
return this.baseUrl;
|
|
140
360
|
}
|
|
141
|
-
|
|
142
|
-
|
|
361
|
+
/**
|
|
362
|
+
* Execute a request and handle the response consistently
|
|
363
|
+
* This provides unified error handling and response processing
|
|
364
|
+
*
|
|
365
|
+
* @param apiCall - Function that executes the API request
|
|
366
|
+
* @returns Promise with the API response in standardized format
|
|
367
|
+
*/
|
|
368
|
+
async executeRequest(apiCall) {
|
|
369
|
+
return executeRequest(apiCall);
|
|
143
370
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
371
|
+
/**
|
|
372
|
+
* Merge default headers with method-level headers
|
|
373
|
+
* Method-level headers take precedence over default headers
|
|
374
|
+
* Automatically applies SDK-specific header transformations
|
|
375
|
+
*
|
|
376
|
+
* @param methodHeaders - Headers passed to the specific method call
|
|
377
|
+
* @returns Merged headers object with proper HTTP header names
|
|
378
|
+
*/
|
|
379
|
+
mergeHeaders(methodHeaders) {
|
|
380
|
+
return mergeAndTransformHeaders(this.config.defaultHeaders, methodHeaders, this.headerTransformations);
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Set default headers for the client
|
|
384
|
+
*
|
|
385
|
+
* @param headers - Default headers to set
|
|
386
|
+
*/
|
|
387
|
+
setDefaultHeaders(headers) {
|
|
388
|
+
this.config.defaultHeaders = headers;
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Get current default headers
|
|
392
|
+
*
|
|
393
|
+
* @returns Current default headers
|
|
394
|
+
*/
|
|
395
|
+
getDefaultHeaders() {
|
|
396
|
+
return this.config.defaultHeaders;
|
|
147
397
|
}
|
|
148
398
|
};
|
|
149
399
|
/**
|
|
150
|
-
*
|
|
400
|
+
* Generic URL utility functions for any SDK
|
|
151
401
|
*/
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
return
|
|
162
|
-
}
|
|
163
|
-
async setAccessToken(token) {
|
|
164
|
-
if (typeof localStorage !== "undefined") localStorage.setItem(this.accessTokenKey, token);
|
|
165
|
-
}
|
|
166
|
-
async getRefreshToken() {
|
|
167
|
-
if (typeof localStorage === "undefined") return null;
|
|
168
|
-
return localStorage.getItem(this.refreshTokenKey);
|
|
169
|
-
}
|
|
170
|
-
async setRefreshToken(token) {
|
|
171
|
-
if (typeof localStorage !== "undefined") localStorage.setItem(this.refreshTokenKey, token);
|
|
172
|
-
}
|
|
173
|
-
async clearTokens() {
|
|
174
|
-
if (typeof localStorage !== "undefined") {
|
|
175
|
-
localStorage.removeItem(this.accessTokenKey);
|
|
176
|
-
localStorage.removeItem(this.refreshTokenKey);
|
|
177
|
-
}
|
|
402
|
+
/**
|
|
403
|
+
* Extract pathname from URL
|
|
404
|
+
* Useful for middleware that needs to inspect request paths
|
|
405
|
+
*/
|
|
406
|
+
function getPathnameFromUrl(url) {
|
|
407
|
+
try {
|
|
408
|
+
const urlObj = new URL(url);
|
|
409
|
+
return urlObj.pathname;
|
|
410
|
+
} catch {
|
|
411
|
+
return url.split("?")[0] || url;
|
|
178
412
|
}
|
|
179
|
-
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
//#endregion
|
|
416
|
+
//#region src/lib/jwt-utils.ts
|
|
180
417
|
/**
|
|
181
|
-
*
|
|
418
|
+
* Decode and extract user information from a JWT token
|
|
419
|
+
*
|
|
420
|
+
* @param token - The JWT token to decode
|
|
421
|
+
* @returns User information or null if token is invalid
|
|
182
422
|
*/
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
423
|
+
function extractUserInfoFromToken(token) {
|
|
424
|
+
try {
|
|
425
|
+
const payload = decodeJwt(token);
|
|
426
|
+
return {
|
|
427
|
+
id: payload.ulid,
|
|
428
|
+
email: payload.email,
|
|
429
|
+
phone: payload.phone,
|
|
430
|
+
username: payload.username,
|
|
431
|
+
firstName: payload.first_name,
|
|
432
|
+
lastName: payload.last_name,
|
|
433
|
+
storeId: payload.store_id,
|
|
434
|
+
isLoggedIn: payload.is_logged_in,
|
|
435
|
+
isAnonymous: !payload.is_logged_in,
|
|
436
|
+
customerId: payload.customer_id,
|
|
437
|
+
customerGroupId: payload.customer_group_id,
|
|
438
|
+
anonymousId: payload.anonymous_id,
|
|
439
|
+
tokenExpiry: /* @__PURE__ */ new Date(payload.exp * 1e3),
|
|
440
|
+
tokenIssuedAt: /* @__PURE__ */ new Date(payload.iat * 1e3)
|
|
198
441
|
};
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return this.getCookie(this.accessTokenKey);
|
|
202
|
-
}
|
|
203
|
-
async setAccessToken(token) {
|
|
204
|
-
this.setCookie(this.accessTokenKey, token);
|
|
205
|
-
}
|
|
206
|
-
async getRefreshToken() {
|
|
207
|
-
return this.getCookie(this.refreshTokenKey);
|
|
208
|
-
}
|
|
209
|
-
async setRefreshToken(token) {
|
|
210
|
-
this.setCookie(this.refreshTokenKey, token);
|
|
211
|
-
}
|
|
212
|
-
async clearTokens() {
|
|
213
|
-
this.deleteCookie(this.accessTokenKey);
|
|
214
|
-
this.deleteCookie(this.refreshTokenKey);
|
|
215
|
-
}
|
|
216
|
-
getCookie(name) {
|
|
217
|
-
if (typeof document === "undefined") return null;
|
|
218
|
-
const value = `; ${document.cookie}`;
|
|
219
|
-
const parts = value.split(`; ${name}=`);
|
|
220
|
-
if (parts.length === 2) {
|
|
221
|
-
const cookieValue = parts.pop()?.split(";").shift();
|
|
222
|
-
return cookieValue ? decodeURIComponent(cookieValue) : null;
|
|
223
|
-
}
|
|
442
|
+
} catch (error) {
|
|
443
|
+
console.warn("Failed to decode JWT token:", error);
|
|
224
444
|
return null;
|
|
225
445
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Check if a JWT token is expired
|
|
449
|
+
*
|
|
450
|
+
* @param token - The JWT token to check
|
|
451
|
+
* @param bufferSeconds - Buffer time in seconds (default: 30)
|
|
452
|
+
* @returns True if token is expired or will expire within buffer time
|
|
453
|
+
*/
|
|
454
|
+
function isTokenExpired(token, bufferSeconds = 30) {
|
|
455
|
+
try {
|
|
456
|
+
const payload = decodeJwt(token);
|
|
457
|
+
if (!payload.exp) return true;
|
|
458
|
+
const currentTime = Math.floor(Date.now() / 1e3);
|
|
459
|
+
const expiryTime = payload.exp;
|
|
460
|
+
return currentTime >= expiryTime - bufferSeconds;
|
|
461
|
+
} catch (error) {
|
|
462
|
+
console.warn("Failed to decode JWT token:", error);
|
|
463
|
+
return true;
|
|
243
464
|
}
|
|
244
|
-
}
|
|
465
|
+
}
|
|
245
466
|
/**
|
|
246
|
-
*
|
|
467
|
+
* Get the user ID from a JWT token
|
|
468
|
+
*
|
|
469
|
+
* @param token - The JWT token
|
|
470
|
+
* @returns User ID (ulid) or null if token is invalid
|
|
247
471
|
*/
|
|
248
|
-
function
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
Authorization: `Bearer ${currentAccessToken}`
|
|
294
|
-
}
|
|
295
|
-
});
|
|
296
|
-
if (!response.ok) throw new Error(`Anonymous token fallback failed: ${response.status}`);
|
|
297
|
-
const data = await response.json();
|
|
298
|
-
newTokens = data.content;
|
|
299
|
-
console.info(`Token refreshed via anonymous fallback (${reason}) - user may need to re-authenticate for privileged operations`);
|
|
300
|
-
}
|
|
301
|
-
await config.tokenStorage.setAccessToken(newTokens.access_token);
|
|
302
|
-
await config.tokenStorage.setRefreshToken(newTokens.refresh_token);
|
|
303
|
-
config.onTokensUpdated?.(newTokens.access_token, newTokens.refresh_token);
|
|
304
|
-
} catch (error) {
|
|
305
|
-
console.error("Token refresh failed:", error);
|
|
306
|
-
await config.tokenStorage.clearTokens();
|
|
307
|
-
config.onTokensCleared?.();
|
|
308
|
-
throw error;
|
|
309
|
-
} finally {
|
|
310
|
-
isRefreshing = false;
|
|
311
|
-
refreshPromise = null;
|
|
312
|
-
}
|
|
313
|
-
})();
|
|
314
|
-
return refreshPromise;
|
|
315
|
-
};
|
|
316
|
-
return {
|
|
317
|
-
async onRequest({ request }) {
|
|
318
|
-
const pathname = getPathnameFromUrl(request.url);
|
|
319
|
-
await assessTokenStateOnce();
|
|
320
|
-
if (isAnonymousAuthEndpoint(pathname)) {
|
|
321
|
-
if (config.apiKey) request.headers.set("X-Api-Key", config.apiKey);
|
|
322
|
-
const existingToken = await config.tokenStorage.getAccessToken();
|
|
323
|
-
if (existingToken && !isTokenExpired(existingToken) && isUserLoggedIn(existingToken)) return new Response(JSON.stringify({
|
|
324
|
-
message: "Cannot create anonymous session while authenticated",
|
|
325
|
-
success: false,
|
|
326
|
-
code: "USER_ALREADY_AUTHENTICATED"
|
|
327
|
-
}), {
|
|
328
|
-
status: 400,
|
|
329
|
-
headers: { "Content-Type": "application/json" }
|
|
330
|
-
});
|
|
331
|
-
if (existingToken) request.headers.set("Authorization", `Bearer ${existingToken}`);
|
|
332
|
-
return request;
|
|
333
|
-
}
|
|
334
|
-
let accessToken = await config.tokenStorage.getAccessToken();
|
|
335
|
-
if (!accessToken) try {
|
|
336
|
-
const response = await fetch(`${config.baseUrl}/auth/anonymous`, {
|
|
337
|
-
method: "POST",
|
|
338
|
-
headers: {
|
|
339
|
-
"Content-Type": "application/json",
|
|
340
|
-
...config.apiKey && { "X-Api-Key": config.apiKey }
|
|
341
|
-
}
|
|
342
|
-
});
|
|
343
|
-
if (response.ok) {
|
|
344
|
-
const data = await response.json();
|
|
345
|
-
const tokens = data.content;
|
|
346
|
-
if (tokens?.access_token && tokens?.refresh_token) {
|
|
347
|
-
await config.tokenStorage.setAccessToken(tokens.access_token);
|
|
348
|
-
await config.tokenStorage.setRefreshToken(tokens.refresh_token);
|
|
349
|
-
accessToken = tokens.access_token;
|
|
350
|
-
config.onTokensUpdated?.(tokens.access_token, tokens.refresh_token);
|
|
351
|
-
console.info("Automatically created anonymous session for first API request");
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
} catch (error) {
|
|
355
|
-
console.warn("Failed to automatically create anonymous tokens:", error);
|
|
356
|
-
}
|
|
357
|
-
if (accessToken && isTokenExpired(accessToken)) try {
|
|
358
|
-
await refreshTokens();
|
|
359
|
-
accessToken = await config.tokenStorage.getAccessToken();
|
|
360
|
-
} catch (error) {}
|
|
361
|
-
if (accessToken) request.headers.set("Authorization", `Bearer ${accessToken}`);
|
|
362
|
-
return request;
|
|
363
|
-
},
|
|
364
|
-
async onResponse({ request, response }) {
|
|
365
|
-
const pathname = getPathnameFromUrl(request.url);
|
|
366
|
-
if (response.ok) {
|
|
367
|
-
if (isTokenReturningEndpoint(pathname) || isAnonymousAuthEndpoint(pathname)) try {
|
|
368
|
-
const data = await response.clone().json();
|
|
369
|
-
const content = data.content;
|
|
370
|
-
if (content?.access_token && content?.refresh_token) {
|
|
371
|
-
await config.tokenStorage.setAccessToken(content.access_token);
|
|
372
|
-
await config.tokenStorage.setRefreshToken(content.refresh_token);
|
|
373
|
-
config.onTokensUpdated?.(content.access_token, content.refresh_token);
|
|
374
|
-
}
|
|
375
|
-
} catch (error) {
|
|
376
|
-
console.warn("Failed to extract tokens from response:", error);
|
|
377
|
-
}
|
|
378
|
-
else if (isLogoutEndpoint(pathname)) {
|
|
379
|
-
await config.tokenStorage.clearTokens();
|
|
380
|
-
config.onTokensCleared?.();
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
if (response.status === 401 && !isAnonymousAuthEndpoint(pathname)) {
|
|
384
|
-
const currentToken = await config.tokenStorage.getAccessToken();
|
|
385
|
-
if (currentToken && isTokenExpired(currentToken, 0)) try {
|
|
386
|
-
await refreshTokens();
|
|
387
|
-
const newToken = await config.tokenStorage.getAccessToken();
|
|
388
|
-
if (newToken) {
|
|
389
|
-
const retryRequest = request.clone();
|
|
390
|
-
retryRequest.headers.set("Authorization", `Bearer ${newToken}`);
|
|
391
|
-
return fetch(retryRequest);
|
|
392
|
-
}
|
|
393
|
-
} catch (error) {
|
|
394
|
-
console.warn("Token refresh failed on 401 response:", error);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
return response;
|
|
398
|
-
}
|
|
399
|
-
};
|
|
472
|
+
function getUserIdFromToken(token) {
|
|
473
|
+
const userInfo = extractUserInfoFromToken(token);
|
|
474
|
+
return userInfo?.id || null;
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Check if user is logged in based on JWT token
|
|
478
|
+
*
|
|
479
|
+
* @param token - The JWT token
|
|
480
|
+
* @returns True if user is logged in, false otherwise
|
|
481
|
+
*/
|
|
482
|
+
function isUserLoggedIn(token) {
|
|
483
|
+
const userInfo = extractUserInfoFromToken(token);
|
|
484
|
+
return userInfo?.isLoggedIn || false;
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Check if user is anonymous based on JWT token
|
|
488
|
+
*
|
|
489
|
+
* @param token - The JWT token
|
|
490
|
+
* @returns True if user is anonymous, false otherwise
|
|
491
|
+
*/
|
|
492
|
+
function isUserAnonymous(token) {
|
|
493
|
+
const userInfo = extractUserInfoFromToken(token);
|
|
494
|
+
return userInfo?.isAnonymous || true;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
//#endregion
|
|
498
|
+
//#region src/lib/auth-utils.ts
|
|
499
|
+
/**
|
|
500
|
+
* Check if a URL path is an auth endpoint that should use API key
|
|
501
|
+
*/
|
|
502
|
+
function isAnonymousAuthEndpoint(pathname) {
|
|
503
|
+
return pathname.endsWith("/auth/anonymous");
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Check if a URL path is a login/register endpoint that returns tokens
|
|
507
|
+
*/
|
|
508
|
+
function isTokenReturningEndpoint(pathname) {
|
|
509
|
+
const tokenEndpoints = [
|
|
510
|
+
"/auth/login/password",
|
|
511
|
+
"/auth/register/phone",
|
|
512
|
+
"/auth/register/email",
|
|
513
|
+
"/auth/verify-otp",
|
|
514
|
+
"/auth/refresh-token"
|
|
515
|
+
];
|
|
516
|
+
return tokenEndpoints.some((endpoint) => pathname.endsWith(endpoint));
|
|
400
517
|
}
|
|
401
518
|
/**
|
|
402
|
-
*
|
|
519
|
+
* Check if a URL path is the logout endpoint
|
|
403
520
|
*/
|
|
404
|
-
function
|
|
405
|
-
|
|
406
|
-
return createAuthMiddleware({
|
|
407
|
-
tokenStorage,
|
|
408
|
-
apiKey: options.apiKey,
|
|
409
|
-
baseUrl: options.baseUrl,
|
|
410
|
-
onTokensUpdated: options.onTokensUpdated,
|
|
411
|
-
onTokensCleared: options.onTokensCleared
|
|
412
|
-
});
|
|
521
|
+
function isLogoutEndpoint(pathname) {
|
|
522
|
+
return pathname.endsWith("/auth/logout");
|
|
413
523
|
}
|
|
414
524
|
|
|
415
525
|
//#endregion
|
|
416
|
-
//#region src/lib/
|
|
526
|
+
//#region src/lib/middleware.ts
|
|
417
527
|
/**
|
|
418
|
-
*
|
|
528
|
+
* Simple in-memory token storage implementation
|
|
419
529
|
*/
|
|
420
|
-
var
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
return Object.fromEntries(response.headers.entries());
|
|
426
|
-
}
|
|
427
|
-
/**
|
|
428
|
-
* Get specific header value
|
|
429
|
-
*/
|
|
430
|
-
static getHeader(response, name) {
|
|
431
|
-
return response.headers.get(name);
|
|
432
|
-
}
|
|
433
|
-
/**
|
|
434
|
-
* Check if response was successful
|
|
435
|
-
*/
|
|
436
|
-
static isSuccess(response) {
|
|
437
|
-
return response.ok;
|
|
530
|
+
var MemoryTokenStorage = class {
|
|
531
|
+
accessToken = null;
|
|
532
|
+
refreshToken = null;
|
|
533
|
+
async getAccessToken() {
|
|
534
|
+
return this.accessToken;
|
|
438
535
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
*/
|
|
442
|
-
static getMetadata(response) {
|
|
443
|
-
return {
|
|
444
|
-
status: response.status,
|
|
445
|
-
statusText: response.statusText,
|
|
446
|
-
ok: response.ok,
|
|
447
|
-
url: response.url,
|
|
448
|
-
redirected: response.redirected,
|
|
449
|
-
type: response.type,
|
|
450
|
-
headers: Object.fromEntries(response.headers.entries())
|
|
451
|
-
};
|
|
536
|
+
async setAccessToken(token) {
|
|
537
|
+
this.accessToken = token;
|
|
452
538
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
* Note: This can only be called once per response
|
|
456
|
-
*/
|
|
457
|
-
static async getText(response) {
|
|
458
|
-
const cloned = response.clone();
|
|
459
|
-
return await cloned.text();
|
|
539
|
+
async getRefreshToken() {
|
|
540
|
+
return this.refreshToken;
|
|
460
541
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
* Note: This can only be called once per response
|
|
464
|
-
*/
|
|
465
|
-
static async getJSON(response) {
|
|
466
|
-
const cloned = response.clone();
|
|
467
|
-
return await cloned.json();
|
|
542
|
+
async setRefreshToken(token) {
|
|
543
|
+
this.refreshToken = token;
|
|
468
544
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
static format(response) {
|
|
473
|
-
const metadata = this.getMetadata(response);
|
|
474
|
-
return `${metadata.status} ${metadata.statusText} - ${metadata.url}`;
|
|
545
|
+
async clearTokens() {
|
|
546
|
+
this.accessToken = null;
|
|
547
|
+
this.refreshToken = null;
|
|
475
548
|
}
|
|
476
549
|
};
|
|
477
550
|
/**
|
|
478
|
-
*
|
|
551
|
+
* Browser localStorage token storage implementation
|
|
479
552
|
*/
|
|
480
|
-
var
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
constructor(
|
|
484
|
-
this.
|
|
485
|
-
|
|
486
|
-
if (data) console.log(data);
|
|
487
|
-
});
|
|
553
|
+
var BrowserTokenStorage = class {
|
|
554
|
+
accessTokenKey;
|
|
555
|
+
refreshTokenKey;
|
|
556
|
+
constructor(prefix = "storefront_") {
|
|
557
|
+
this.accessTokenKey = `${prefix}access_token`;
|
|
558
|
+
this.refreshTokenKey = `${prefix}refresh_token`;
|
|
488
559
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
logRequest(request, requestBody) {
|
|
493
|
-
this.logger("info", "API Request Debug Info", {
|
|
494
|
-
method: request.method,
|
|
495
|
-
url: request.url,
|
|
496
|
-
headers: Object.fromEntries(request.headers.entries()),
|
|
497
|
-
body: requestBody,
|
|
498
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
499
|
-
});
|
|
560
|
+
async getAccessToken() {
|
|
561
|
+
if (typeof localStorage === "undefined") return null;
|
|
562
|
+
return localStorage.getItem(this.accessTokenKey);
|
|
500
563
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
*/
|
|
504
|
-
async logResponse(response, responseBody) {
|
|
505
|
-
if (responseBody && typeof responseBody === "string") this.responseTextCache.set(response.url, responseBody);
|
|
506
|
-
this.logger("info", "API Response Debug Info", {
|
|
507
|
-
url: response.url,
|
|
508
|
-
status: response.status,
|
|
509
|
-
statusText: response.statusText,
|
|
510
|
-
ok: response.ok,
|
|
511
|
-
headers: Object.fromEntries(response.headers.entries()),
|
|
512
|
-
redirected: response.redirected,
|
|
513
|
-
type: response.type,
|
|
514
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
515
|
-
});
|
|
516
|
-
if (responseBody) this.logger("info", "API Response Data", {
|
|
517
|
-
data: responseBody,
|
|
518
|
-
contentType: response.headers.get("content-type"),
|
|
519
|
-
contentLength: response.headers.get("content-length")
|
|
520
|
-
});
|
|
564
|
+
async setAccessToken(token) {
|
|
565
|
+
if (typeof localStorage !== "undefined") localStorage.setItem(this.accessTokenKey, token);
|
|
521
566
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
logError(message, error) {
|
|
526
|
-
this.logger("error", message, error);
|
|
567
|
+
async getRefreshToken() {
|
|
568
|
+
if (typeof localStorage === "undefined") return null;
|
|
569
|
+
return localStorage.getItem(this.refreshTokenKey);
|
|
527
570
|
}
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
*/
|
|
531
|
-
getCachedResponseText(url) {
|
|
532
|
-
return this.responseTextCache.get(url) || null;
|
|
571
|
+
async setRefreshToken(token) {
|
|
572
|
+
if (typeof localStorage !== "undefined") localStorage.setItem(this.refreshTokenKey, token);
|
|
533
573
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
574
|
+
async clearTokens() {
|
|
575
|
+
if (typeof localStorage !== "undefined") {
|
|
576
|
+
localStorage.removeItem(this.accessTokenKey);
|
|
577
|
+
localStorage.removeItem(this.refreshTokenKey);
|
|
578
|
+
}
|
|
539
579
|
}
|
|
540
580
|
};
|
|
541
581
|
/**
|
|
542
|
-
*
|
|
582
|
+
* Cookie-based token storage implementation
|
|
543
583
|
*/
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
584
|
+
var CookieTokenStorage = class {
|
|
585
|
+
accessTokenKey;
|
|
586
|
+
refreshTokenKey;
|
|
587
|
+
options;
|
|
588
|
+
constructor(options = {}) {
|
|
589
|
+
const prefix = options.prefix || "storefront_";
|
|
590
|
+
this.accessTokenKey = `${prefix}access_token`;
|
|
591
|
+
this.refreshTokenKey = `${prefix}refresh_token`;
|
|
592
|
+
this.options = {
|
|
593
|
+
maxAge: options.maxAge || 10080 * 60,
|
|
594
|
+
path: options.path || "/",
|
|
595
|
+
domain: options.domain,
|
|
596
|
+
secure: options.secure ?? (typeof window !== "undefined" && window.location?.protocol === "https:"),
|
|
597
|
+
sameSite: options.sameSite || "Lax",
|
|
598
|
+
httpOnly: false
|
|
599
|
+
};
|
|
555
600
|
}
|
|
556
|
-
|
|
601
|
+
async getAccessToken() {
|
|
602
|
+
return this.getCookie(this.accessTokenKey);
|
|
603
|
+
}
|
|
604
|
+
async setAccessToken(token) {
|
|
605
|
+
this.setCookie(this.accessTokenKey, token);
|
|
606
|
+
}
|
|
607
|
+
async getRefreshToken() {
|
|
608
|
+
return this.getCookie(this.refreshTokenKey);
|
|
609
|
+
}
|
|
610
|
+
async setRefreshToken(token) {
|
|
611
|
+
this.setCookie(this.refreshTokenKey, token);
|
|
612
|
+
}
|
|
613
|
+
async clearTokens() {
|
|
614
|
+
this.deleteCookie(this.accessTokenKey);
|
|
615
|
+
this.deleteCookie(this.refreshTokenKey);
|
|
616
|
+
}
|
|
617
|
+
getCookie(name) {
|
|
618
|
+
if (typeof document === "undefined") return null;
|
|
619
|
+
const value = `; ${document.cookie}`;
|
|
620
|
+
const parts = value.split(`; ${name}=`);
|
|
621
|
+
if (parts.length === 2) {
|
|
622
|
+
const cookieValue = parts.pop()?.split(";").shift();
|
|
623
|
+
return cookieValue ? decodeURIComponent(cookieValue) : null;
|
|
624
|
+
}
|
|
625
|
+
return null;
|
|
626
|
+
}
|
|
627
|
+
setCookie(name, value) {
|
|
628
|
+
if (typeof document === "undefined") return;
|
|
629
|
+
const encodedValue = encodeURIComponent(value);
|
|
630
|
+
let cookieString = `${name}=${encodedValue}`;
|
|
631
|
+
if (this.options.maxAge) cookieString += `; Max-Age=${this.options.maxAge}`;
|
|
632
|
+
if (this.options.path) cookieString += `; Path=${this.options.path}`;
|
|
633
|
+
if (this.options.domain) cookieString += `; Domain=${this.options.domain}`;
|
|
634
|
+
if (this.options.secure) cookieString += `; Secure`;
|
|
635
|
+
if (this.options.sameSite) cookieString += `; SameSite=${this.options.sameSite}`;
|
|
636
|
+
document.cookie = cookieString;
|
|
637
|
+
}
|
|
638
|
+
deleteCookie(name) {
|
|
639
|
+
if (typeof document === "undefined") return;
|
|
640
|
+
let cookieString = `${name}=; Max-Age=0`;
|
|
641
|
+
if (this.options.path) cookieString += `; Path=${this.options.path}`;
|
|
642
|
+
if (this.options.domain) cookieString += `; Domain=${this.options.domain}`;
|
|
643
|
+
document.cookie = cookieString;
|
|
644
|
+
}
|
|
645
|
+
};
|
|
557
646
|
/**
|
|
558
|
-
* Create
|
|
647
|
+
* Create authentication middleware for openapi-fetch
|
|
559
648
|
*/
|
|
560
|
-
function
|
|
561
|
-
|
|
649
|
+
function createAuthMiddleware(config) {
|
|
650
|
+
let isRefreshing = false;
|
|
651
|
+
let refreshPromise = null;
|
|
652
|
+
let hasAssessedTokens = false;
|
|
653
|
+
const assessTokenStateOnce = async () => {
|
|
654
|
+
if (hasAssessedTokens) return;
|
|
655
|
+
hasAssessedTokens = true;
|
|
656
|
+
try {
|
|
657
|
+
const accessToken = await config.tokenStorage.getAccessToken();
|
|
658
|
+
const refreshToken = await config.tokenStorage.getRefreshToken();
|
|
659
|
+
if (!accessToken && refreshToken) {
|
|
660
|
+
await config.tokenStorage.clearTokens();
|
|
661
|
+
console.info("Cleaned up orphaned refresh token");
|
|
662
|
+
}
|
|
663
|
+
} catch (error) {
|
|
664
|
+
console.warn("Token state assessment failed:", error);
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
const refreshTokens = async () => {
|
|
668
|
+
if (isRefreshing && refreshPromise) return refreshPromise;
|
|
669
|
+
isRefreshing = true;
|
|
670
|
+
refreshPromise = (async () => {
|
|
671
|
+
try {
|
|
672
|
+
const refreshToken = await config.tokenStorage.getRefreshToken();
|
|
673
|
+
let newTokens;
|
|
674
|
+
if (refreshToken && !isTokenExpired(refreshToken)) if (config.refreshTokenFn) newTokens = await config.refreshTokenFn(refreshToken);
|
|
675
|
+
else {
|
|
676
|
+
const response = await fetch(`${config.baseUrl}/auth/refresh-token`, {
|
|
677
|
+
method: "POST",
|
|
678
|
+
headers: { "Content-Type": "application/json" },
|
|
679
|
+
body: JSON.stringify({ refresh_token: refreshToken })
|
|
680
|
+
});
|
|
681
|
+
if (!response.ok) throw new Error(`Token refresh failed: ${response.status}`);
|
|
682
|
+
const data = await response.json();
|
|
683
|
+
newTokens = data.content;
|
|
684
|
+
}
|
|
685
|
+
else {
|
|
686
|
+
const currentAccessToken = await config.tokenStorage.getAccessToken();
|
|
687
|
+
if (!currentAccessToken) throw new Error("No tokens available for refresh");
|
|
688
|
+
const reason = refreshToken ? "refresh token expired" : "no refresh token available";
|
|
689
|
+
const response = await fetch(`${config.baseUrl}/auth/anonymous`, {
|
|
690
|
+
method: "POST",
|
|
691
|
+
headers: {
|
|
692
|
+
"Content-Type": "application/json",
|
|
693
|
+
...config.apiKey && { "X-Api-Key": config.apiKey },
|
|
694
|
+
Authorization: `Bearer ${currentAccessToken}`
|
|
695
|
+
}
|
|
696
|
+
});
|
|
697
|
+
if (!response.ok) throw new Error(`Anonymous token fallback failed: ${response.status}`);
|
|
698
|
+
const data = await response.json();
|
|
699
|
+
newTokens = data.content;
|
|
700
|
+
console.info(`Token refreshed via anonymous fallback (${reason}) - user may need to re-authenticate for privileged operations`);
|
|
701
|
+
}
|
|
702
|
+
await config.tokenStorage.setAccessToken(newTokens.access_token);
|
|
703
|
+
await config.tokenStorage.setRefreshToken(newTokens.refresh_token);
|
|
704
|
+
config.onTokensUpdated?.(newTokens.access_token, newTokens.refresh_token);
|
|
705
|
+
} catch (error) {
|
|
706
|
+
console.error("Token refresh failed:", error);
|
|
707
|
+
await config.tokenStorage.clearTokens();
|
|
708
|
+
config.onTokensCleared?.();
|
|
709
|
+
throw error;
|
|
710
|
+
} finally {
|
|
711
|
+
isRefreshing = false;
|
|
712
|
+
refreshPromise = null;
|
|
713
|
+
}
|
|
714
|
+
})();
|
|
715
|
+
return refreshPromise;
|
|
716
|
+
};
|
|
562
717
|
return {
|
|
563
718
|
async onRequest({ request }) {
|
|
564
|
-
const
|
|
565
|
-
|
|
719
|
+
const pathname = getPathnameFromUrl(request.url);
|
|
720
|
+
await assessTokenStateOnce();
|
|
721
|
+
if (isAnonymousAuthEndpoint(pathname)) {
|
|
722
|
+
if (config.apiKey) request.headers.set("X-Api-Key", config.apiKey);
|
|
723
|
+
const existingToken = await config.tokenStorage.getAccessToken();
|
|
724
|
+
if (existingToken && !isTokenExpired(existingToken) && isUserLoggedIn(existingToken)) return new Response(JSON.stringify({
|
|
725
|
+
message: "Cannot create anonymous session while authenticated",
|
|
726
|
+
success: false,
|
|
727
|
+
code: "USER_ALREADY_AUTHENTICATED"
|
|
728
|
+
}), {
|
|
729
|
+
status: 400,
|
|
730
|
+
headers: { "Content-Type": "application/json" }
|
|
731
|
+
});
|
|
732
|
+
if (existingToken) request.headers.set("Authorization", `Bearer ${existingToken}`);
|
|
733
|
+
return request;
|
|
734
|
+
}
|
|
735
|
+
let accessToken = await config.tokenStorage.getAccessToken();
|
|
736
|
+
if (!accessToken) try {
|
|
737
|
+
const response = await fetch(`${config.baseUrl}/auth/anonymous`, {
|
|
738
|
+
method: "POST",
|
|
739
|
+
headers: {
|
|
740
|
+
"Content-Type": "application/json",
|
|
741
|
+
...config.apiKey && { "X-Api-Key": config.apiKey }
|
|
742
|
+
}
|
|
743
|
+
});
|
|
744
|
+
if (response.ok) {
|
|
745
|
+
const data = await response.json();
|
|
746
|
+
const tokens = data.content;
|
|
747
|
+
if (tokens?.access_token && tokens?.refresh_token) {
|
|
748
|
+
await config.tokenStorage.setAccessToken(tokens.access_token);
|
|
749
|
+
await config.tokenStorage.setRefreshToken(tokens.refresh_token);
|
|
750
|
+
accessToken = tokens.access_token;
|
|
751
|
+
config.onTokensUpdated?.(tokens.access_token, tokens.refresh_token);
|
|
752
|
+
console.info("Automatically created anonymous session for first API request");
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
} catch (error) {
|
|
756
|
+
console.warn("Failed to automatically create anonymous tokens:", error);
|
|
757
|
+
}
|
|
758
|
+
if (accessToken && isTokenExpired(accessToken)) try {
|
|
759
|
+
await refreshTokens();
|
|
760
|
+
accessToken = await config.tokenStorage.getAccessToken();
|
|
761
|
+
} catch (error) {}
|
|
762
|
+
if (accessToken) request.headers.set("Authorization", `Bearer ${accessToken}`);
|
|
566
763
|
return request;
|
|
567
764
|
},
|
|
568
|
-
async onResponse({ response }) {
|
|
569
|
-
const
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
765
|
+
async onResponse({ request, response }) {
|
|
766
|
+
const pathname = getPathnameFromUrl(request.url);
|
|
767
|
+
if (response.ok) {
|
|
768
|
+
if (isTokenReturningEndpoint(pathname) || isAnonymousAuthEndpoint(pathname)) try {
|
|
769
|
+
const data = await response.clone().json();
|
|
770
|
+
const content = data.content;
|
|
771
|
+
if (content?.access_token && content?.refresh_token) {
|
|
772
|
+
await config.tokenStorage.setAccessToken(content.access_token);
|
|
773
|
+
await config.tokenStorage.setRefreshToken(content.refresh_token);
|
|
774
|
+
config.onTokensUpdated?.(content.access_token, content.refresh_token);
|
|
775
|
+
}
|
|
776
|
+
} catch (error) {
|
|
777
|
+
console.warn("Failed to extract tokens from response:", error);
|
|
778
|
+
}
|
|
779
|
+
else if (isLogoutEndpoint(pathname)) {
|
|
780
|
+
await config.tokenStorage.clearTokens();
|
|
781
|
+
config.onTokensCleared?.();
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
if (response.status === 401 && !isAnonymousAuthEndpoint(pathname)) {
|
|
785
|
+
const currentToken = await config.tokenStorage.getAccessToken();
|
|
786
|
+
if (currentToken && isTokenExpired(currentToken, 0)) try {
|
|
787
|
+
await refreshTokens();
|
|
788
|
+
const newToken = await config.tokenStorage.getAccessToken();
|
|
789
|
+
if (newToken) {
|
|
790
|
+
const retryRequest = request.clone();
|
|
791
|
+
retryRequest.headers.set("Authorization", `Bearer ${newToken}`);
|
|
792
|
+
return fetch(retryRequest);
|
|
793
|
+
}
|
|
794
|
+
} catch (error) {
|
|
795
|
+
console.warn("Token refresh failed on 401 response:", error);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
577
798
|
return response;
|
|
578
|
-
},
|
|
579
|
-
async onError({ error, request }) {
|
|
580
|
-
debugLogger.logError("API Request Failed", {
|
|
581
|
-
error: {
|
|
582
|
-
name: error instanceof Error ? error.name : "Unknown",
|
|
583
|
-
message: error instanceof Error ? error.message : String(error),
|
|
584
|
-
stack: error instanceof Error ? error.stack : void 0
|
|
585
|
-
},
|
|
586
|
-
request: {
|
|
587
|
-
method: request.method,
|
|
588
|
-
url: request.url,
|
|
589
|
-
headers: Object.fromEntries(request.headers.entries())
|
|
590
|
-
},
|
|
591
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
592
|
-
});
|
|
593
|
-
throw error;
|
|
594
799
|
}
|
|
595
800
|
};
|
|
596
801
|
}
|
|
597
|
-
|
|
598
|
-
//#endregion
|
|
599
|
-
//#region src/lib/header-utils.ts
|
|
600
|
-
/**
|
|
601
|
-
* Mapping of SDK header parameter names to actual HTTP header names
|
|
602
|
-
* Only include headers that need transformation - others pass through as-is
|
|
603
|
-
*/
|
|
604
|
-
const HEADER_TRANSFORMATIONS = { customer_group_id: "x-customer-group-id" };
|
|
605
|
-
/**
|
|
606
|
-
* Transform SDK header parameters to actual HTTP header names
|
|
607
|
-
* Headers not in the transformation map are passed through unchanged
|
|
608
|
-
*
|
|
609
|
-
* @param headers - Headers object with SDK parameter names
|
|
610
|
-
* @returns Headers object with actual HTTP header names
|
|
611
|
-
*/
|
|
612
|
-
function transformHeaders(headers) {
|
|
613
|
-
const transformed = {};
|
|
614
|
-
for (const [key, value] of Object.entries(headers)) if (value !== void 0) {
|
|
615
|
-
const headerName = HEADER_TRANSFORMATIONS[key] || key;
|
|
616
|
-
transformed[headerName] = value;
|
|
617
|
-
}
|
|
618
|
-
return transformed;
|
|
619
|
-
}
|
|
620
802
|
/**
|
|
621
|
-
*
|
|
622
|
-
* Method-level headers take precedence over default headers
|
|
623
|
-
* Automatically transforms SDK parameter names to HTTP header names
|
|
624
|
-
*
|
|
625
|
-
* @param defaultHeaders - Default headers from SDK configuration
|
|
626
|
-
* @param methodHeaders - Headers passed to the specific method call
|
|
627
|
-
* @returns Merged headers object with proper HTTP header names
|
|
803
|
+
* Helper function to create auth middleware with sensible defaults
|
|
628
804
|
*/
|
|
629
|
-
function
|
|
630
|
-
const
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
if (merged[key] === void 0) delete merged[key];
|
|
805
|
+
function createDefaultAuthMiddleware(options) {
|
|
806
|
+
const tokenStorage = options.tokenStorage || (typeof localStorage !== "undefined" ? new BrowserTokenStorage() : new MemoryTokenStorage());
|
|
807
|
+
return createAuthMiddleware({
|
|
808
|
+
tokenStorage,
|
|
809
|
+
apiKey: options.apiKey,
|
|
810
|
+
baseUrl: options.baseUrl,
|
|
811
|
+
onTokensUpdated: options.onTokensUpdated,
|
|
812
|
+
onTokensCleared: options.onTokensCleared
|
|
638
813
|
});
|
|
639
|
-
return merged;
|
|
640
814
|
}
|
|
641
815
|
|
|
642
816
|
//#endregion
|
|
643
|
-
//#region src/lib/
|
|
817
|
+
//#region src/lib/url-utils.ts
|
|
644
818
|
/**
|
|
645
|
-
*
|
|
819
|
+
* URL utility functions for the Storefront SDK
|
|
820
|
+
*/
|
|
821
|
+
/**
|
|
822
|
+
* Available API environments for Commerce Engine
|
|
646
823
|
*/
|
|
647
824
|
let Environment = /* @__PURE__ */ function(Environment$1) {
|
|
648
825
|
/**
|
|
@@ -656,12 +833,26 @@ let Environment = /* @__PURE__ */ function(Environment$1) {
|
|
|
656
833
|
return Environment$1;
|
|
657
834
|
}({});
|
|
658
835
|
/**
|
|
659
|
-
*
|
|
836
|
+
* Build base URL for Storefront API
|
|
660
837
|
*/
|
|
661
|
-
|
|
662
|
-
|
|
838
|
+
function buildStorefrontURL(config) {
|
|
839
|
+
if (config.baseUrl) return config.baseUrl;
|
|
840
|
+
const env = config.environment || Environment.Production;
|
|
841
|
+
switch (env) {
|
|
842
|
+
case Environment.Staging: return `https://staging.api.commercengine.io/api/v1/${config.storeId}/storefront`;
|
|
843
|
+
case Environment.Production:
|
|
844
|
+
default: return `https://prod.api.commercengine.io/api/v1/${config.storeId}/storefront`;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
//#endregion
|
|
849
|
+
//#region src/lib/client.ts
|
|
850
|
+
/**
|
|
851
|
+
* Storefront API client that extends the generic BaseAPIClient
|
|
852
|
+
* Adds Commerce Engine specific authentication and token management
|
|
853
|
+
*/
|
|
854
|
+
var StorefrontAPIClient = class extends BaseAPIClient {
|
|
663
855
|
config;
|
|
664
|
-
baseUrl;
|
|
665
856
|
initializationPromise = null;
|
|
666
857
|
/**
|
|
667
858
|
* Create a new StorefrontAPIClient
|
|
@@ -669,74 +860,51 @@ var StorefrontAPIClient = class {
|
|
|
669
860
|
* @param config - Configuration for the API client
|
|
670
861
|
*/
|
|
671
862
|
constructor(config) {
|
|
863
|
+
const baseUrl = buildStorefrontURL({
|
|
864
|
+
storeId: config.storeId,
|
|
865
|
+
environment: config.environment,
|
|
866
|
+
baseUrl: config.baseUrl
|
|
867
|
+
});
|
|
868
|
+
const headerTransformations = { customer_group_id: "x-customer-group-id" };
|
|
869
|
+
super({
|
|
870
|
+
baseUrl,
|
|
871
|
+
timeout: config.timeout,
|
|
872
|
+
defaultHeaders: config.defaultHeaders,
|
|
873
|
+
debug: config.debug,
|
|
874
|
+
logger: config.logger
|
|
875
|
+
}, baseUrl, headerTransformations);
|
|
672
876
|
this.config = { ...config };
|
|
673
|
-
this.
|
|
674
|
-
|
|
675
|
-
|
|
877
|
+
this.setupStorefrontAuth();
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* Set up Storefront-specific authentication middleware
|
|
881
|
+
*/
|
|
882
|
+
setupStorefrontAuth() {
|
|
883
|
+
const config = this.config;
|
|
884
|
+
if (config.tokenStorage) {
|
|
676
885
|
const authMiddleware = createDefaultAuthMiddleware({
|
|
677
|
-
apiKey:
|
|
678
|
-
baseUrl: this.
|
|
679
|
-
tokenStorage:
|
|
680
|
-
onTokensUpdated:
|
|
681
|
-
onTokensCleared:
|
|
886
|
+
apiKey: config.apiKey,
|
|
887
|
+
baseUrl: this.getBaseUrl(),
|
|
888
|
+
tokenStorage: config.tokenStorage,
|
|
889
|
+
onTokensUpdated: config.onTokensUpdated,
|
|
890
|
+
onTokensCleared: config.onTokensCleared
|
|
682
891
|
});
|
|
683
892
|
this.client.use(authMiddleware);
|
|
684
|
-
if (
|
|
685
|
-
this.initializationPromise = this.initializeTokens(
|
|
686
|
-
|
|
687
|
-
|
|
893
|
+
if (config.accessToken) {
|
|
894
|
+
this.initializationPromise = this.initializeTokens(config.accessToken, config.refreshToken);
|
|
895
|
+
config.accessToken = void 0;
|
|
896
|
+
config.refreshToken = void 0;
|
|
688
897
|
}
|
|
689
898
|
} else this.client.use({ onRequest: async ({ request }) => {
|
|
690
899
|
const pathname = getPathnameFromUrl(request.url);
|
|
691
900
|
if (isAnonymousAuthEndpoint(pathname)) {
|
|
692
|
-
if (
|
|
693
|
-
if (
|
|
901
|
+
if (config.apiKey) request.headers.set("X-Api-Key", config.apiKey);
|
|
902
|
+
if (config.accessToken) request.headers.set("Authorization", `Bearer ${config.accessToken}`);
|
|
694
903
|
return request;
|
|
695
904
|
}
|
|
696
|
-
if (
|
|
905
|
+
if (config.accessToken) request.headers.set("Authorization", `Bearer ${config.accessToken}`);
|
|
697
906
|
return request;
|
|
698
907
|
} });
|
|
699
|
-
if (this.config.timeout) this.setupTimeoutMiddleware();
|
|
700
|
-
if (this.config.debug) {
|
|
701
|
-
const debugMiddleware = createDebugMiddleware(this.config.logger);
|
|
702
|
-
this.client.use(debugMiddleware);
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
/**
|
|
706
|
-
* Set up timeout middleware
|
|
707
|
-
*/
|
|
708
|
-
setupTimeoutMiddleware() {
|
|
709
|
-
this.client.use({ onRequest: async ({ request }) => {
|
|
710
|
-
const controller = new AbortController();
|
|
711
|
-
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
712
|
-
if (request.signal) request.signal.addEventListener("abort", () => controller.abort());
|
|
713
|
-
const newRequest = new Request(request, { signal: controller.signal });
|
|
714
|
-
controller.signal.addEventListener("abort", () => clearTimeout(timeoutId));
|
|
715
|
-
return newRequest;
|
|
716
|
-
} });
|
|
717
|
-
}
|
|
718
|
-
/**
|
|
719
|
-
* Constructs the base URL from the configuration
|
|
720
|
-
*
|
|
721
|
-
* @param config - The client configuration
|
|
722
|
-
* @returns The constructed base URL
|
|
723
|
-
*/
|
|
724
|
-
getBaseUrlFromConfig(config) {
|
|
725
|
-
if (config.baseUrl) return config.baseUrl;
|
|
726
|
-
const env = config.environment || Environment.Production;
|
|
727
|
-
switch (env) {
|
|
728
|
-
case Environment.Staging: return `https://staging.api.commercengine.io/api/v1/${config.storeId}/storefront`;
|
|
729
|
-
case Environment.Production:
|
|
730
|
-
default: return `https://prod.api.commercengine.io/api/v1/${config.storeId}/storefront`;
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
/**
|
|
734
|
-
* Get the base URL of the API
|
|
735
|
-
*
|
|
736
|
-
* @returns The base URL of the API
|
|
737
|
-
*/
|
|
738
|
-
getBaseUrl() {
|
|
739
|
-
return this.baseUrl;
|
|
740
908
|
}
|
|
741
909
|
/**
|
|
742
910
|
* Get the authorization header value
|
|
@@ -798,48 +966,6 @@ var StorefrontAPIClient = class {
|
|
|
798
966
|
this.config.apiKey = void 0;
|
|
799
967
|
}
|
|
800
968
|
/**
|
|
801
|
-
* Execute a request and handle the response
|
|
802
|
-
*
|
|
803
|
-
* @param apiCall - Function that executes the API request
|
|
804
|
-
* @returns Promise with the API response
|
|
805
|
-
*/
|
|
806
|
-
async executeRequest(apiCall) {
|
|
807
|
-
try {
|
|
808
|
-
const { data, error, response } = await apiCall();
|
|
809
|
-
if (error) return {
|
|
810
|
-
data: null,
|
|
811
|
-
error,
|
|
812
|
-
response
|
|
813
|
-
};
|
|
814
|
-
if (data && data.content !== void 0) return {
|
|
815
|
-
data: data.content,
|
|
816
|
-
error: null,
|
|
817
|
-
response
|
|
818
|
-
};
|
|
819
|
-
return {
|
|
820
|
-
data,
|
|
821
|
-
error: null,
|
|
822
|
-
response
|
|
823
|
-
};
|
|
824
|
-
} catch (err) {
|
|
825
|
-
const mockResponse = new Response(null, {
|
|
826
|
-
status: 0,
|
|
827
|
-
statusText: "Network Error"
|
|
828
|
-
});
|
|
829
|
-
const errorResult = {
|
|
830
|
-
data: null,
|
|
831
|
-
error: {
|
|
832
|
-
success: false,
|
|
833
|
-
code: "NETWORK_ERROR",
|
|
834
|
-
message: "Network error occurred",
|
|
835
|
-
error: err
|
|
836
|
-
},
|
|
837
|
-
response: mockResponse
|
|
838
|
-
};
|
|
839
|
-
return errorResult;
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
/**
|
|
843
969
|
* Initialize tokens in storage (private helper method)
|
|
844
970
|
*/
|
|
845
971
|
async initializeTokens(accessToken, refreshToken) {
|
|
@@ -852,16 +978,6 @@ var StorefrontAPIClient = class {
|
|
|
852
978
|
console.warn("Failed to initialize tokens in storage:", error);
|
|
853
979
|
}
|
|
854
980
|
}
|
|
855
|
-
/**
|
|
856
|
-
* Merge default headers with method-level headers
|
|
857
|
-
* Method-level headers take precedence over default headers
|
|
858
|
-
*
|
|
859
|
-
* @param methodHeaders - Headers passed to the specific method call
|
|
860
|
-
* @returns Merged headers object with proper HTTP header names
|
|
861
|
-
*/
|
|
862
|
-
mergeHeaders(methodHeaders) {
|
|
863
|
-
return mergeHeaders(this.config.defaultHeaders, methodHeaders);
|
|
864
|
-
}
|
|
865
981
|
};
|
|
866
982
|
|
|
867
983
|
//#endregion
|
|
@@ -3646,11 +3762,16 @@ var StorefrontSDK = class {
|
|
|
3646
3762
|
*/
|
|
3647
3763
|
store;
|
|
3648
3764
|
/**
|
|
3765
|
+
* Centrally stored default headers for consistency
|
|
3766
|
+
*/
|
|
3767
|
+
defaultHeaders;
|
|
3768
|
+
/**
|
|
3649
3769
|
* Create a new StorefrontSDK instance
|
|
3650
3770
|
*
|
|
3651
3771
|
* @param options - Configuration options for the SDK
|
|
3652
3772
|
*/
|
|
3653
3773
|
constructor(options) {
|
|
3774
|
+
this.defaultHeaders = options.defaultHeaders;
|
|
3654
3775
|
const config = {
|
|
3655
3776
|
storeId: options.storeId,
|
|
3656
3777
|
environment: options.environment,
|
|
@@ -3810,26 +3931,23 @@ var StorefrontSDK = class {
|
|
|
3810
3931
|
* @param headers - Default headers to set (only supported headers allowed)
|
|
3811
3932
|
*/
|
|
3812
3933
|
setDefaultHeaders(headers) {
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
this.
|
|
3818
|
-
this.
|
|
3819
|
-
this.
|
|
3820
|
-
this.
|
|
3821
|
-
this.
|
|
3822
|
-
this.shipping["config"] = newConfig;
|
|
3823
|
-
this.order["config"] = newConfig;
|
|
3824
|
-
this.store["config"] = newConfig;
|
|
3934
|
+
this.defaultHeaders = headers;
|
|
3935
|
+
this.catalog.setDefaultHeaders(headers);
|
|
3936
|
+
this.cart.setDefaultHeaders(headers);
|
|
3937
|
+
this.auth.setDefaultHeaders(headers);
|
|
3938
|
+
this.customer.setDefaultHeaders(headers);
|
|
3939
|
+
this.helpers.setDefaultHeaders(headers);
|
|
3940
|
+
this.shipping.setDefaultHeaders(headers);
|
|
3941
|
+
this.order.setDefaultHeaders(headers);
|
|
3942
|
+
this.store.setDefaultHeaders(headers);
|
|
3825
3943
|
}
|
|
3826
3944
|
/**
|
|
3827
3945
|
* Get current default headers
|
|
3828
3946
|
*
|
|
3829
|
-
* @returns Current default headers
|
|
3947
|
+
* @returns Current default headers from central storage (always consistent)
|
|
3830
3948
|
*/
|
|
3831
3949
|
getDefaultHeaders() {
|
|
3832
|
-
return this.
|
|
3950
|
+
return this.defaultHeaders;
|
|
3833
3951
|
}
|
|
3834
3952
|
};
|
|
3835
3953
|
var src_default = StorefrontSDK;
|