@commercengine/storefront-sdk 0.8.2 → 0.9.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/dist/index.d.ts +5617 -5161
- package/dist/index.iife.js +886 -768
- package/dist/index.iife.js.map +1 -1
- package/dist/index.js +71 -365
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.iife.js
CHANGED
|
@@ -24,8 +24,8 @@ function createClient(clientOptions) {
|
|
|
24
24
|
...typeof globalQuerySerializer === "object" ? globalQuerySerializer : {},
|
|
25
25
|
...requestQuerySerializer
|
|
26
26
|
});
|
|
27
|
-
const serializedBody = body === void 0 ? void 0 : bodySerializer(body, mergeHeaders
|
|
28
|
-
const finalHeaders = mergeHeaders
|
|
27
|
+
const serializedBody = body === void 0 ? void 0 : bodySerializer(body, mergeHeaders(baseHeaders, headers, params.header));
|
|
28
|
+
const finalHeaders = mergeHeaders(serializedBody === void 0 || serializedBody instanceof FormData ? {} : { "Content-Type": "application/json" }, baseHeaders, headers, params.header);
|
|
29
29
|
const requestInit = {
|
|
30
30
|
redirect: "follow",
|
|
31
31
|
...baseOptions,
|
|
@@ -355,7 +355,7 @@ function createFinalURL(pathname, options) {
|
|
|
355
355
|
if (search) finalURL += `?${search}`;
|
|
356
356
|
return finalURL;
|
|
357
357
|
}
|
|
358
|
-
function mergeHeaders
|
|
358
|
+
function mergeHeaders(...allHeaders) {
|
|
359
359
|
const finalHeaders = new Headers();
|
|
360
360
|
for (const h of allHeaders) {
|
|
361
361
|
if (!h || typeof h !== "object") continue;
|
|
@@ -372,739 +372,916 @@ function removeTrailingSlash(url) {
|
|
|
372
372
|
}
|
|
373
373
|
|
|
374
374
|
//#endregion
|
|
375
|
-
//#region
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
const bytes = new Uint8Array(binary.length);
|
|
386
|
-
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
387
|
-
return bytes;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
//#endregion
|
|
391
|
-
//#region ../../node_modules/.pnpm/jose@6.0.13/node_modules/jose/dist/webapi/util/base64url.js
|
|
392
|
-
function decode(input) {
|
|
393
|
-
if (Uint8Array.fromBase64) return Uint8Array.fromBase64(typeof input === "string" ? input : decoder.decode(input), { alphabet: "base64url" });
|
|
394
|
-
let encoded = input;
|
|
395
|
-
if (encoded instanceof Uint8Array) encoded = decoder.decode(encoded);
|
|
396
|
-
encoded = encoded.replace(/-/g, "+").replace(/_/g, "/").replace(/\s/g, "");
|
|
397
|
-
try {
|
|
398
|
-
return decodeBase64(encoded);
|
|
399
|
-
} catch {
|
|
400
|
-
throw new TypeError("The input to be decoded is not correctly encoded.");
|
|
375
|
+
//#region ../sdk-core/dist/index.js
|
|
376
|
+
/**
|
|
377
|
+
* Response utilities for debugging and working with Response objects
|
|
378
|
+
*/
|
|
379
|
+
var ResponseUtils = class {
|
|
380
|
+
/**
|
|
381
|
+
* Get response headers as a plain object
|
|
382
|
+
*/
|
|
383
|
+
static getHeaders(response) {
|
|
384
|
+
return Object.fromEntries(response.headers.entries());
|
|
401
385
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
static code = "ERR_JOSE_GENERIC";
|
|
408
|
-
code = "ERR_JOSE_GENERIC";
|
|
409
|
-
constructor(message, options) {
|
|
410
|
-
super(message, options);
|
|
411
|
-
this.name = this.constructor.name;
|
|
412
|
-
Error.captureStackTrace?.(this, this.constructor);
|
|
386
|
+
/**
|
|
387
|
+
* Get specific header value
|
|
388
|
+
*/
|
|
389
|
+
static getHeader(response, name) {
|
|
390
|
+
return response.headers.get(name);
|
|
413
391
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
392
|
+
/**
|
|
393
|
+
* Check if response was successful
|
|
394
|
+
*/
|
|
395
|
+
static isSuccess(response) {
|
|
396
|
+
return response.ok;
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Get response metadata
|
|
400
|
+
*/
|
|
401
|
+
static getMetadata(response) {
|
|
402
|
+
return {
|
|
403
|
+
status: response.status,
|
|
404
|
+
statusText: response.statusText,
|
|
405
|
+
ok: response.ok,
|
|
406
|
+
url: response.url,
|
|
407
|
+
redirected: response.redirected,
|
|
408
|
+
type: response.type,
|
|
409
|
+
headers: Object.fromEntries(response.headers.entries())
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Clone and read response as text (useful for debugging)
|
|
414
|
+
* Note: This can only be called once per response
|
|
415
|
+
*/
|
|
416
|
+
static async getText(response) {
|
|
417
|
+
const cloned = response.clone();
|
|
418
|
+
return await cloned.text();
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Clone and read response as JSON (useful for debugging)
|
|
422
|
+
* Note: This can only be called once per response
|
|
423
|
+
*/
|
|
424
|
+
static async getJSON(response) {
|
|
425
|
+
const cloned = response.clone();
|
|
426
|
+
return await cloned.json();
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Format response information for debugging
|
|
430
|
+
*/
|
|
431
|
+
static format(response) {
|
|
432
|
+
const metadata = this.getMetadata(response);
|
|
433
|
+
return `${metadata.status} ${metadata.statusText} - ${metadata.url}`;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Format response for logging purposes (enhanced version)
|
|
437
|
+
*/
|
|
438
|
+
static formatResponse(response) {
|
|
439
|
+
return {
|
|
440
|
+
status: response.status,
|
|
441
|
+
statusText: response.statusText,
|
|
442
|
+
url: response.url,
|
|
443
|
+
ok: response.ok
|
|
444
|
+
};
|
|
425
445
|
}
|
|
426
446
|
};
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
return Object.getPrototypeOf(input) === proto;
|
|
439
|
-
};
|
|
440
|
-
|
|
441
|
-
//#endregion
|
|
442
|
-
//#region ../../node_modules/.pnpm/jose@6.0.13/node_modules/jose/dist/webapi/util/decode_jwt.js
|
|
443
|
-
function decodeJwt(jwt) {
|
|
444
|
-
if (typeof jwt !== "string") throw new JWTInvalid("JWTs must use Compact JWS serialization, JWT must be a string");
|
|
445
|
-
const { 1: payload, length } = jwt.split(".");
|
|
446
|
-
if (length === 5) throw new JWTInvalid("Only JWTs using Compact JWS serialization can be decoded");
|
|
447
|
-
if (length !== 3) throw new JWTInvalid("Invalid JWT");
|
|
448
|
-
if (!payload) throw new JWTInvalid("JWTs must contain a payload");
|
|
449
|
-
let decoded;
|
|
450
|
-
try {
|
|
451
|
-
decoded = decode(payload);
|
|
452
|
-
} catch {
|
|
453
|
-
throw new JWTInvalid("Failed to base64url decode the payload");
|
|
447
|
+
/**
|
|
448
|
+
* Debug logging utilities
|
|
449
|
+
*/
|
|
450
|
+
var DebugLogger = class {
|
|
451
|
+
logger;
|
|
452
|
+
responseTextCache = /* @__PURE__ */ new Map();
|
|
453
|
+
constructor(logger) {
|
|
454
|
+
this.logger = logger || ((level, message, data) => {
|
|
455
|
+
console.log(`[${level.toUpperCase()}]`, message);
|
|
456
|
+
if (data) console.log(data);
|
|
457
|
+
});
|
|
454
458
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
459
|
+
/**
|
|
460
|
+
* Log debug information about API request
|
|
461
|
+
*/
|
|
462
|
+
logRequest(request, requestBody) {
|
|
463
|
+
this.logger("info", "API Request Debug Info", {
|
|
464
|
+
method: request.method,
|
|
465
|
+
url: request.url,
|
|
466
|
+
headers: Object.fromEntries(request.headers.entries()),
|
|
467
|
+
body: requestBody,
|
|
468
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
469
|
+
});
|
|
460
470
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
471
|
+
/**
|
|
472
|
+
* Log debug information about API response
|
|
473
|
+
*/
|
|
474
|
+
async logResponse(response, responseBody) {
|
|
475
|
+
if (responseBody && typeof responseBody === "string") this.responseTextCache.set(response.url, responseBody);
|
|
476
|
+
this.logger("info", "API Response Debug Info", {
|
|
477
|
+
url: response.url,
|
|
478
|
+
status: response.status,
|
|
479
|
+
statusText: response.statusText,
|
|
480
|
+
ok: response.ok,
|
|
481
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
482
|
+
redirected: response.redirected,
|
|
483
|
+
type: response.type,
|
|
484
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
485
|
+
});
|
|
486
|
+
if (responseBody) this.logger("info", "API Response Data", {
|
|
487
|
+
data: responseBody,
|
|
488
|
+
contentType: response.headers.get("content-type"),
|
|
489
|
+
contentLength: response.headers.get("content-length")
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Log error information
|
|
494
|
+
*/
|
|
495
|
+
logError(message, error) {
|
|
496
|
+
this.logger("error", message, error);
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Get cached response text for a URL (if available)
|
|
500
|
+
*/
|
|
501
|
+
getCachedResponseText(url) {
|
|
502
|
+
return this.responseTextCache.get(url) || null;
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Clear cached response texts
|
|
506
|
+
*/
|
|
507
|
+
clearCache() {
|
|
508
|
+
this.responseTextCache.clear();
|
|
509
|
+
}
|
|
510
|
+
info(message, data) {
|
|
511
|
+
this.logger("info", message, data);
|
|
512
|
+
}
|
|
513
|
+
warn(message, data) {
|
|
514
|
+
this.logger("warn", message, data);
|
|
515
|
+
}
|
|
516
|
+
error(message, data) {
|
|
517
|
+
this.logger("error", message, data);
|
|
518
|
+
}
|
|
519
|
+
};
|
|
467
520
|
/**
|
|
468
|
-
*
|
|
469
|
-
*
|
|
470
|
-
* @param token - The JWT token to decode
|
|
471
|
-
* @returns User information or null if token is invalid
|
|
521
|
+
* Extract request body for logging
|
|
472
522
|
*/
|
|
473
|
-
function
|
|
523
|
+
async function extractRequestBody(request) {
|
|
524
|
+
if (request.method === "GET" || request.method === "HEAD") return null;
|
|
474
525
|
try {
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
firstName: payload.first_name,
|
|
482
|
-
lastName: payload.last_name,
|
|
483
|
-
storeId: payload.store_id,
|
|
484
|
-
isLoggedIn: payload.is_logged_in,
|
|
485
|
-
isAnonymous: !payload.is_logged_in,
|
|
486
|
-
customerId: payload.customer_id,
|
|
487
|
-
customerGroupId: payload.customer_group_id,
|
|
488
|
-
anonymousId: payload.anonymous_id,
|
|
489
|
-
tokenExpiry: /* @__PURE__ */ new Date(payload.exp * 1e3),
|
|
490
|
-
tokenIssuedAt: /* @__PURE__ */ new Date(payload.iat * 1e3)
|
|
491
|
-
};
|
|
526
|
+
const clonedRequest = request.clone();
|
|
527
|
+
const contentType = request.headers.get("content-type")?.toLowerCase();
|
|
528
|
+
if (contentType?.startsWith("application/json")) return await clonedRequest.json();
|
|
529
|
+
else if (contentType?.startsWith("multipart/form-data")) return "[FormData - cannot display]";
|
|
530
|
+
else if (contentType?.startsWith("text/")) return await clonedRequest.text();
|
|
531
|
+
return "[Request body - unknown format]";
|
|
492
532
|
} catch (error) {
|
|
493
|
-
|
|
494
|
-
return null;
|
|
533
|
+
return "[Request body unavailable]";
|
|
495
534
|
}
|
|
496
535
|
}
|
|
497
536
|
/**
|
|
498
|
-
*
|
|
499
|
-
*
|
|
500
|
-
* @param token - The JWT token to check
|
|
501
|
-
* @param bufferSeconds - Buffer time in seconds (default: 30)
|
|
502
|
-
* @returns True if token is expired or will expire within buffer time
|
|
537
|
+
* Create debug middleware for openapi-fetch (internal use)
|
|
538
|
+
* Enhanced version that combines original functionality with duration tracking
|
|
503
539
|
*/
|
|
504
|
-
function
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
540
|
+
function createDebugMiddleware(logger) {
|
|
541
|
+
const debugLogger = new DebugLogger(logger);
|
|
542
|
+
return {
|
|
543
|
+
async onRequest({ request }) {
|
|
544
|
+
request.__debugStartTime = Date.now();
|
|
545
|
+
const requestBody = await extractRequestBody(request);
|
|
546
|
+
debugLogger.logRequest(request, requestBody);
|
|
547
|
+
return request;
|
|
548
|
+
},
|
|
549
|
+
async onResponse({ request, response }) {
|
|
550
|
+
const startTime = request.__debugStartTime;
|
|
551
|
+
const duration = startTime ? Date.now() - startTime : 0;
|
|
552
|
+
const cloned = response.clone();
|
|
553
|
+
let responseBody = null;
|
|
554
|
+
try {
|
|
555
|
+
const contentType = response.headers.get("content-type")?.toLowerCase();
|
|
556
|
+
if (contentType?.startsWith("application/json")) responseBody = await cloned.json();
|
|
557
|
+
else if (contentType?.startsWith("text/")) responseBody = await cloned.text();
|
|
558
|
+
} catch (error) {}
|
|
559
|
+
await debugLogger.logResponse(response, responseBody);
|
|
560
|
+
if (duration > 0) debugLogger.info(`Request completed in ${duration}ms`, {
|
|
561
|
+
url: request.url,
|
|
562
|
+
method: request.method
|
|
563
|
+
});
|
|
564
|
+
return response;
|
|
565
|
+
},
|
|
566
|
+
async onError({ error, request }) {
|
|
567
|
+
debugLogger.logError("API Request Failed", {
|
|
568
|
+
error: {
|
|
569
|
+
name: error instanceof Error ? error.name : "Unknown",
|
|
570
|
+
message: error instanceof Error ? error.message : String(error),
|
|
571
|
+
stack: error instanceof Error ? error.stack : void 0
|
|
572
|
+
},
|
|
573
|
+
request: {
|
|
574
|
+
method: request.method,
|
|
575
|
+
url: request.url,
|
|
576
|
+
headers: Object.fromEntries(request.headers.entries())
|
|
577
|
+
},
|
|
578
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
579
|
+
});
|
|
580
|
+
throw error;
|
|
581
|
+
}
|
|
582
|
+
};
|
|
525
583
|
}
|
|
526
584
|
/**
|
|
527
|
-
*
|
|
528
|
-
*
|
|
529
|
-
* @param token - The JWT token
|
|
530
|
-
* @returns True if user is logged in, false otherwise
|
|
585
|
+
* Timeout middleware for Commerce Engine SDKs
|
|
531
586
|
*/
|
|
532
|
-
function isUserLoggedIn(token) {
|
|
533
|
-
const userInfo = extractUserInfoFromToken(token);
|
|
534
|
-
return userInfo?.isLoggedIn || false;
|
|
535
|
-
}
|
|
536
587
|
/**
|
|
537
|
-
*
|
|
588
|
+
* Create timeout middleware for openapi-fetch
|
|
589
|
+
* Adds configurable request timeout functionality
|
|
538
590
|
*
|
|
539
|
-
* @param
|
|
540
|
-
* @returns
|
|
591
|
+
* @param timeoutMs - Timeout duration in milliseconds
|
|
592
|
+
* @returns Middleware object with onRequest handler
|
|
541
593
|
*/
|
|
542
|
-
function
|
|
543
|
-
|
|
544
|
-
|
|
594
|
+
function createTimeoutMiddleware(timeoutMs) {
|
|
595
|
+
return { onRequest: async ({ request }) => {
|
|
596
|
+
const controller = new AbortController();
|
|
597
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
598
|
+
if (request.signal) request.signal.addEventListener("abort", () => controller.abort());
|
|
599
|
+
const newRequest = new Request(request, { signal: controller.signal });
|
|
600
|
+
controller.signal.addEventListener("abort", () => clearTimeout(timeoutId));
|
|
601
|
+
return newRequest;
|
|
602
|
+
} };
|
|
545
603
|
}
|
|
546
|
-
|
|
547
|
-
//#endregion
|
|
548
|
-
//#region src/lib/auth-utils.ts
|
|
549
604
|
/**
|
|
550
|
-
*
|
|
605
|
+
* Transform headers using a transformation mapping
|
|
606
|
+
* Headers not in the transformation map are passed through unchanged
|
|
607
|
+
*
|
|
608
|
+
* @param headers - Headers object with original names
|
|
609
|
+
* @param transformations - Mapping of original names to transformed names
|
|
610
|
+
* @returns Headers object with transformed names
|
|
551
611
|
*/
|
|
552
|
-
function
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
return url.split("?")[0];
|
|
612
|
+
function transformHeaders(headers, transformations) {
|
|
613
|
+
const transformed = {};
|
|
614
|
+
for (const [key, value] of Object.entries(headers)) if (value !== void 0) {
|
|
615
|
+
const headerName = transformations[key] || key;
|
|
616
|
+
transformed[headerName] = value;
|
|
558
617
|
}
|
|
618
|
+
return transformed;
|
|
559
619
|
}
|
|
560
620
|
/**
|
|
561
|
-
*
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
*
|
|
621
|
+
* Merge headers with transformation support
|
|
622
|
+
* Transforms default headers, then merges with method headers
|
|
623
|
+
*
|
|
624
|
+
* @param defaultHeaders - Default headers from SDK configuration
|
|
625
|
+
* @param methodHeaders - Headers passed to the specific method call
|
|
626
|
+
* @param transformations - Mapping for header name transformations
|
|
627
|
+
* @returns Merged headers object with transformations applied
|
|
568
628
|
*/
|
|
569
|
-
function
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
629
|
+
function mergeAndTransformHeaders(defaultHeaders, methodHeaders, transformations) {
|
|
630
|
+
const merged = {};
|
|
631
|
+
if (defaultHeaders && transformations) {
|
|
632
|
+
const transformedDefaults = transformHeaders(defaultHeaders, transformations);
|
|
633
|
+
Object.assign(merged, transformedDefaults);
|
|
634
|
+
} else if (defaultHeaders) Object.assign(merged, defaultHeaders);
|
|
635
|
+
if (methodHeaders) Object.assign(merged, methodHeaders);
|
|
636
|
+
Object.keys(merged).forEach((key) => {
|
|
637
|
+
if (merged[key] === void 0) delete merged[key];
|
|
638
|
+
});
|
|
639
|
+
return merged;
|
|
578
640
|
}
|
|
579
641
|
/**
|
|
580
|
-
*
|
|
642
|
+
* Execute a request and handle the response consistently
|
|
643
|
+
* This provides unified error handling and response processing across all SDKs
|
|
644
|
+
*
|
|
645
|
+
* @param apiCall - Function that executes the API request
|
|
646
|
+
* @returns Promise with the API response in standardized format
|
|
581
647
|
*/
|
|
582
|
-
function
|
|
583
|
-
|
|
648
|
+
async function executeRequest(apiCall) {
|
|
649
|
+
try {
|
|
650
|
+
const { data, error, response } = await apiCall();
|
|
651
|
+
if (error) return {
|
|
652
|
+
data: null,
|
|
653
|
+
error,
|
|
654
|
+
response
|
|
655
|
+
};
|
|
656
|
+
if (data && data.content !== void 0) return {
|
|
657
|
+
data: data.content,
|
|
658
|
+
error: null,
|
|
659
|
+
response
|
|
660
|
+
};
|
|
661
|
+
return {
|
|
662
|
+
data,
|
|
663
|
+
error: null,
|
|
664
|
+
response
|
|
665
|
+
};
|
|
666
|
+
} catch (err) {
|
|
667
|
+
const mockResponse = new Response(null, {
|
|
668
|
+
status: 0,
|
|
669
|
+
statusText: "Network Error"
|
|
670
|
+
});
|
|
671
|
+
const errorResult = {
|
|
672
|
+
data: null,
|
|
673
|
+
error: {
|
|
674
|
+
success: false,
|
|
675
|
+
code: "NETWORK_ERROR",
|
|
676
|
+
message: "Network error occurred",
|
|
677
|
+
error: err
|
|
678
|
+
},
|
|
679
|
+
response: mockResponse
|
|
680
|
+
};
|
|
681
|
+
return errorResult;
|
|
682
|
+
}
|
|
584
683
|
}
|
|
585
|
-
|
|
586
|
-
//#endregion
|
|
587
|
-
//#region src/lib/middleware.ts
|
|
588
684
|
/**
|
|
589
|
-
*
|
|
685
|
+
* Generic base API client that all Commerce Engine SDKs can extend
|
|
686
|
+
* Handles common functionality like middleware setup, request execution, and header management
|
|
687
|
+
* Does NOT include token management - that's SDK-specific
|
|
688
|
+
*
|
|
689
|
+
* @template TPaths - OpenAPI paths type
|
|
690
|
+
* @template THeaders - Supported default headers type
|
|
590
691
|
*/
|
|
591
|
-
var
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
this.
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
this.
|
|
608
|
-
this.
|
|
692
|
+
var BaseAPIClient = class {
|
|
693
|
+
client;
|
|
694
|
+
config;
|
|
695
|
+
baseUrl;
|
|
696
|
+
headerTransformations;
|
|
697
|
+
/**
|
|
698
|
+
* Create a new BaseAPIClient
|
|
699
|
+
*
|
|
700
|
+
* @param config - Configuration for the API client
|
|
701
|
+
* @param baseUrl - The base URL for the API (must be provided by subclass)
|
|
702
|
+
* @param headerTransformations - Header name transformations for this SDK
|
|
703
|
+
*/
|
|
704
|
+
constructor(config, baseUrl, headerTransformations = {}) {
|
|
705
|
+
this.config = { ...config };
|
|
706
|
+
this.headerTransformations = headerTransformations;
|
|
707
|
+
this.baseUrl = baseUrl;
|
|
708
|
+
this.client = createClient({ baseUrl: this.baseUrl });
|
|
709
|
+
this.setupMiddleware();
|
|
609
710
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
this.
|
|
619
|
-
|
|
711
|
+
/**
|
|
712
|
+
* Set up all middleware for the client
|
|
713
|
+
*/
|
|
714
|
+
setupMiddleware() {
|
|
715
|
+
if (this.config.timeout) {
|
|
716
|
+
const timeoutMiddleware = createTimeoutMiddleware(this.config.timeout);
|
|
717
|
+
this.client.use(timeoutMiddleware);
|
|
718
|
+
}
|
|
719
|
+
if (this.config.debug) {
|
|
720
|
+
const debugMiddleware = createDebugMiddleware(this.config.logger);
|
|
721
|
+
this.client.use(debugMiddleware);
|
|
722
|
+
}
|
|
620
723
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
724
|
+
/**
|
|
725
|
+
* Get the base URL of the API
|
|
726
|
+
*
|
|
727
|
+
* @returns The base URL of the API
|
|
728
|
+
*/
|
|
729
|
+
getBaseUrl() {
|
|
730
|
+
return this.baseUrl;
|
|
624
731
|
}
|
|
625
|
-
|
|
626
|
-
|
|
732
|
+
/**
|
|
733
|
+
* Execute a request and handle the response consistently
|
|
734
|
+
* This provides unified error handling and response processing
|
|
735
|
+
*
|
|
736
|
+
* @param apiCall - Function that executes the API request
|
|
737
|
+
* @returns Promise with the API response in standardized format
|
|
738
|
+
*/
|
|
739
|
+
async executeRequest(apiCall) {
|
|
740
|
+
return executeRequest(apiCall);
|
|
627
741
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
742
|
+
/**
|
|
743
|
+
* Merge default headers with method-level headers
|
|
744
|
+
* Method-level headers take precedence over default headers
|
|
745
|
+
* Automatically applies SDK-specific header transformations
|
|
746
|
+
*
|
|
747
|
+
* @param methodHeaders - Headers passed to the specific method call
|
|
748
|
+
* @returns Merged headers object with proper HTTP header names
|
|
749
|
+
*/
|
|
750
|
+
mergeHeaders(methodHeaders) {
|
|
751
|
+
return mergeAndTransformHeaders(this.config.defaultHeaders, methodHeaders, this.headerTransformations);
|
|
631
752
|
}
|
|
632
|
-
|
|
633
|
-
|
|
753
|
+
/**
|
|
754
|
+
* Set default headers for the client
|
|
755
|
+
*
|
|
756
|
+
* @param headers - Default headers to set
|
|
757
|
+
*/
|
|
758
|
+
setDefaultHeaders(headers) {
|
|
759
|
+
this.config.defaultHeaders = headers;
|
|
634
760
|
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
761
|
+
/**
|
|
762
|
+
* Get current default headers
|
|
763
|
+
*
|
|
764
|
+
* @returns Current default headers
|
|
765
|
+
*/
|
|
766
|
+
getDefaultHeaders() {
|
|
767
|
+
return this.config.defaultHeaders;
|
|
640
768
|
}
|
|
641
769
|
};
|
|
642
770
|
/**
|
|
643
|
-
*
|
|
771
|
+
* Generic URL utility functions for any SDK
|
|
644
772
|
*/
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
path: options.path || "/",
|
|
656
|
-
domain: options.domain,
|
|
657
|
-
secure: options.secure ?? (typeof window !== "undefined" && window.location?.protocol === "https:"),
|
|
658
|
-
sameSite: options.sameSite || "Lax",
|
|
659
|
-
httpOnly: false
|
|
660
|
-
};
|
|
661
|
-
}
|
|
662
|
-
async getAccessToken() {
|
|
663
|
-
return this.getCookie(this.accessTokenKey);
|
|
664
|
-
}
|
|
665
|
-
async setAccessToken(token) {
|
|
666
|
-
this.setCookie(this.accessTokenKey, token);
|
|
667
|
-
}
|
|
668
|
-
async getRefreshToken() {
|
|
669
|
-
return this.getCookie(this.refreshTokenKey);
|
|
773
|
+
/**
|
|
774
|
+
* Extract pathname from URL
|
|
775
|
+
* Useful for middleware that needs to inspect request paths
|
|
776
|
+
*/
|
|
777
|
+
function getPathnameFromUrl(url) {
|
|
778
|
+
try {
|
|
779
|
+
const urlObj = new URL(url);
|
|
780
|
+
return urlObj.pathname;
|
|
781
|
+
} catch {
|
|
782
|
+
return url.split("?")[0] || url;
|
|
670
783
|
}
|
|
671
|
-
|
|
672
|
-
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
//#endregion
|
|
787
|
+
//#region ../../node_modules/.pnpm/jose@6.0.13/node_modules/jose/dist/webapi/lib/buffer_utils.js
|
|
788
|
+
const encoder = new TextEncoder();
|
|
789
|
+
const decoder = new TextDecoder();
|
|
790
|
+
const MAX_INT32 = 2 ** 32;
|
|
791
|
+
|
|
792
|
+
//#endregion
|
|
793
|
+
//#region ../../node_modules/.pnpm/jose@6.0.13/node_modules/jose/dist/webapi/lib/base64.js
|
|
794
|
+
function decodeBase64(encoded) {
|
|
795
|
+
if (Uint8Array.fromBase64) return Uint8Array.fromBase64(encoded);
|
|
796
|
+
const binary = atob(encoded);
|
|
797
|
+
const bytes = new Uint8Array(binary.length);
|
|
798
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
799
|
+
return bytes;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
//#endregion
|
|
803
|
+
//#region ../../node_modules/.pnpm/jose@6.0.13/node_modules/jose/dist/webapi/util/base64url.js
|
|
804
|
+
function decode(input) {
|
|
805
|
+
if (Uint8Array.fromBase64) return Uint8Array.fromBase64(typeof input === "string" ? input : decoder.decode(input), { alphabet: "base64url" });
|
|
806
|
+
let encoded = input;
|
|
807
|
+
if (encoded instanceof Uint8Array) encoded = decoder.decode(encoded);
|
|
808
|
+
encoded = encoded.replace(/-/g, "+").replace(/_/g, "/").replace(/\s/g, "");
|
|
809
|
+
try {
|
|
810
|
+
return decodeBase64(encoded);
|
|
811
|
+
} catch {
|
|
812
|
+
throw new TypeError("The input to be decoded is not correctly encoded.");
|
|
673
813
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
//#endregion
|
|
817
|
+
//#region ../../node_modules/.pnpm/jose@6.0.13/node_modules/jose/dist/webapi/util/errors.js
|
|
818
|
+
var JOSEError = class extends Error {
|
|
819
|
+
static code = "ERR_JOSE_GENERIC";
|
|
820
|
+
code = "ERR_JOSE_GENERIC";
|
|
821
|
+
constructor(message, options) {
|
|
822
|
+
super(message, options);
|
|
823
|
+
this.name = this.constructor.name;
|
|
824
|
+
Error.captureStackTrace?.(this, this.constructor);
|
|
677
825
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
826
|
+
};
|
|
827
|
+
var JWTInvalid = class extends JOSEError {
|
|
828
|
+
static code = "ERR_JWT_INVALID";
|
|
829
|
+
code = "ERR_JWT_INVALID";
|
|
830
|
+
};
|
|
831
|
+
var JWKSMultipleMatchingKeys = class extends JOSEError {
|
|
832
|
+
[Symbol.asyncIterator];
|
|
833
|
+
static code = "ERR_JWKS_MULTIPLE_MATCHING_KEYS";
|
|
834
|
+
code = "ERR_JWKS_MULTIPLE_MATCHING_KEYS";
|
|
835
|
+
constructor(message = "multiple matching keys found in the JSON Web Key Set", options) {
|
|
836
|
+
super(message, options);
|
|
687
837
|
}
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
838
|
+
};
|
|
839
|
+
|
|
840
|
+
//#endregion
|
|
841
|
+
//#region ../../node_modules/.pnpm/jose@6.0.13/node_modules/jose/dist/webapi/lib/is_object.js
|
|
842
|
+
function isObjectLike(value) {
|
|
843
|
+
return typeof value === "object" && value !== null;
|
|
844
|
+
}
|
|
845
|
+
var is_object_default = (input) => {
|
|
846
|
+
if (!isObjectLike(input) || Object.prototype.toString.call(input) !== "[object Object]") return false;
|
|
847
|
+
if (Object.getPrototypeOf(input) === null) return true;
|
|
848
|
+
let proto = input;
|
|
849
|
+
while (Object.getPrototypeOf(proto) !== null) proto = Object.getPrototypeOf(proto);
|
|
850
|
+
return Object.getPrototypeOf(input) === proto;
|
|
851
|
+
};
|
|
852
|
+
|
|
853
|
+
//#endregion
|
|
854
|
+
//#region ../../node_modules/.pnpm/jose@6.0.13/node_modules/jose/dist/webapi/util/decode_jwt.js
|
|
855
|
+
function decodeJwt(jwt) {
|
|
856
|
+
if (typeof jwt !== "string") throw new JWTInvalid("JWTs must use Compact JWS serialization, JWT must be a string");
|
|
857
|
+
const { 1: payload, length } = jwt.split(".");
|
|
858
|
+
if (length === 5) throw new JWTInvalid("Only JWTs using Compact JWS serialization can be decoded");
|
|
859
|
+
if (length !== 3) throw new JWTInvalid("Invalid JWT");
|
|
860
|
+
if (!payload) throw new JWTInvalid("JWTs must contain a payload");
|
|
861
|
+
let decoded;
|
|
862
|
+
try {
|
|
863
|
+
decoded = decode(payload);
|
|
864
|
+
} catch {
|
|
865
|
+
throw new JWTInvalid("Failed to base64url decode the payload");
|
|
698
866
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
document.cookie = cookieString;
|
|
867
|
+
let result;
|
|
868
|
+
try {
|
|
869
|
+
result = JSON.parse(decoder.decode(decoded));
|
|
870
|
+
} catch {
|
|
871
|
+
throw new JWTInvalid("Failed to parse the decoded payload as JSON");
|
|
705
872
|
}
|
|
706
|
-
|
|
873
|
+
if (!is_object_default(result)) throw new JWTInvalid("Invalid JWT Claims Set");
|
|
874
|
+
return result;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
//#endregion
|
|
878
|
+
//#region src/lib/jwt-utils.ts
|
|
707
879
|
/**
|
|
708
|
-
*
|
|
880
|
+
* Decode and extract user information from a JWT token
|
|
881
|
+
*
|
|
882
|
+
* @param token - The JWT token to decode
|
|
883
|
+
* @returns User information or null if token is invalid
|
|
709
884
|
*/
|
|
710
|
-
function
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
const refreshToken = await config.tokenStorage.getRefreshToken();
|
|
734
|
-
let newTokens;
|
|
735
|
-
if (refreshToken && !isTokenExpired(refreshToken)) if (config.refreshTokenFn) newTokens = await config.refreshTokenFn(refreshToken);
|
|
736
|
-
else {
|
|
737
|
-
const response = await fetch(`${config.baseUrl}/auth/refresh-token`, {
|
|
738
|
-
method: "POST",
|
|
739
|
-
headers: { "Content-Type": "application/json" },
|
|
740
|
-
body: JSON.stringify({ refresh_token: refreshToken })
|
|
741
|
-
});
|
|
742
|
-
if (!response.ok) throw new Error(`Token refresh failed: ${response.status}`);
|
|
743
|
-
const data = await response.json();
|
|
744
|
-
newTokens = data.content;
|
|
745
|
-
}
|
|
746
|
-
else {
|
|
747
|
-
const currentAccessToken = await config.tokenStorage.getAccessToken();
|
|
748
|
-
if (!currentAccessToken) throw new Error("No tokens available for refresh");
|
|
749
|
-
const reason = refreshToken ? "refresh token expired" : "no refresh token available";
|
|
750
|
-
const response = await fetch(`${config.baseUrl}/auth/anonymous`, {
|
|
751
|
-
method: "POST",
|
|
752
|
-
headers: {
|
|
753
|
-
"Content-Type": "application/json",
|
|
754
|
-
...config.apiKey && { "X-Api-Key": config.apiKey },
|
|
755
|
-
Authorization: `Bearer ${currentAccessToken}`
|
|
756
|
-
}
|
|
757
|
-
});
|
|
758
|
-
if (!response.ok) throw new Error(`Anonymous token fallback failed: ${response.status}`);
|
|
759
|
-
const data = await response.json();
|
|
760
|
-
newTokens = data.content;
|
|
761
|
-
console.info(`Token refreshed via anonymous fallback (${reason}) - user may need to re-authenticate for privileged operations`);
|
|
762
|
-
}
|
|
763
|
-
await config.tokenStorage.setAccessToken(newTokens.access_token);
|
|
764
|
-
await config.tokenStorage.setRefreshToken(newTokens.refresh_token);
|
|
765
|
-
config.onTokensUpdated?.(newTokens.access_token, newTokens.refresh_token);
|
|
766
|
-
} catch (error) {
|
|
767
|
-
console.error("Token refresh failed:", error);
|
|
768
|
-
await config.tokenStorage.clearTokens();
|
|
769
|
-
config.onTokensCleared?.();
|
|
770
|
-
throw error;
|
|
771
|
-
} finally {
|
|
772
|
-
isRefreshing = false;
|
|
773
|
-
refreshPromise = null;
|
|
774
|
-
}
|
|
775
|
-
})();
|
|
776
|
-
return refreshPromise;
|
|
777
|
-
};
|
|
778
|
-
return {
|
|
779
|
-
async onRequest({ request }) {
|
|
780
|
-
const pathname = getPathnameFromUrl(request.url);
|
|
781
|
-
await assessTokenStateOnce();
|
|
782
|
-
if (isAnonymousAuthEndpoint(pathname)) {
|
|
783
|
-
if (config.apiKey) request.headers.set("X-Api-Key", config.apiKey);
|
|
784
|
-
const existingToken = await config.tokenStorage.getAccessToken();
|
|
785
|
-
if (existingToken && !isTokenExpired(existingToken) && isUserLoggedIn(existingToken)) return new Response(JSON.stringify({
|
|
786
|
-
message: "Cannot create anonymous session while authenticated",
|
|
787
|
-
success: false,
|
|
788
|
-
code: "USER_ALREADY_AUTHENTICATED"
|
|
789
|
-
}), {
|
|
790
|
-
status: 400,
|
|
791
|
-
headers: { "Content-Type": "application/json" }
|
|
792
|
-
});
|
|
793
|
-
if (existingToken) request.headers.set("Authorization", `Bearer ${existingToken}`);
|
|
794
|
-
return request;
|
|
795
|
-
}
|
|
796
|
-
let accessToken = await config.tokenStorage.getAccessToken();
|
|
797
|
-
if (!accessToken) try {
|
|
798
|
-
const response = await fetch(`${config.baseUrl}/auth/anonymous`, {
|
|
799
|
-
method: "POST",
|
|
800
|
-
headers: {
|
|
801
|
-
"Content-Type": "application/json",
|
|
802
|
-
...config.apiKey && { "X-Api-Key": config.apiKey }
|
|
803
|
-
}
|
|
804
|
-
});
|
|
805
|
-
if (response.ok) {
|
|
806
|
-
const data = await response.json();
|
|
807
|
-
const tokens = data.content;
|
|
808
|
-
if (tokens?.access_token && tokens?.refresh_token) {
|
|
809
|
-
await config.tokenStorage.setAccessToken(tokens.access_token);
|
|
810
|
-
await config.tokenStorage.setRefreshToken(tokens.refresh_token);
|
|
811
|
-
accessToken = tokens.access_token;
|
|
812
|
-
config.onTokensUpdated?.(tokens.access_token, tokens.refresh_token);
|
|
813
|
-
console.info("Automatically created anonymous session for first API request");
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
} catch (error) {
|
|
817
|
-
console.warn("Failed to automatically create anonymous tokens:", error);
|
|
818
|
-
}
|
|
819
|
-
if (accessToken && isTokenExpired(accessToken)) try {
|
|
820
|
-
await refreshTokens();
|
|
821
|
-
accessToken = await config.tokenStorage.getAccessToken();
|
|
822
|
-
} catch (error) {}
|
|
823
|
-
if (accessToken) request.headers.set("Authorization", `Bearer ${accessToken}`);
|
|
824
|
-
return request;
|
|
825
|
-
},
|
|
826
|
-
async onResponse({ request, response }) {
|
|
827
|
-
const pathname = getPathnameFromUrl(request.url);
|
|
828
|
-
if (response.ok) {
|
|
829
|
-
if (isTokenReturningEndpoint(pathname) || isAnonymousAuthEndpoint(pathname)) try {
|
|
830
|
-
const data = await response.clone().json();
|
|
831
|
-
const content = data.content;
|
|
832
|
-
if (content?.access_token && content?.refresh_token) {
|
|
833
|
-
await config.tokenStorage.setAccessToken(content.access_token);
|
|
834
|
-
await config.tokenStorage.setRefreshToken(content.refresh_token);
|
|
835
|
-
config.onTokensUpdated?.(content.access_token, content.refresh_token);
|
|
836
|
-
}
|
|
837
|
-
} catch (error) {
|
|
838
|
-
console.warn("Failed to extract tokens from response:", error);
|
|
839
|
-
}
|
|
840
|
-
else if (isLogoutEndpoint(pathname)) {
|
|
841
|
-
await config.tokenStorage.clearTokens();
|
|
842
|
-
config.onTokensCleared?.();
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
if (response.status === 401 && !isAnonymousAuthEndpoint(pathname)) {
|
|
846
|
-
const currentToken = await config.tokenStorage.getAccessToken();
|
|
847
|
-
if (currentToken && isTokenExpired(currentToken, 0)) try {
|
|
848
|
-
await refreshTokens();
|
|
849
|
-
const newToken = await config.tokenStorage.getAccessToken();
|
|
850
|
-
if (newToken) {
|
|
851
|
-
const retryRequest = request.clone();
|
|
852
|
-
retryRequest.headers.set("Authorization", `Bearer ${newToken}`);
|
|
853
|
-
return fetch(retryRequest);
|
|
854
|
-
}
|
|
855
|
-
} catch (error) {
|
|
856
|
-
console.warn("Token refresh failed on 401 response:", error);
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
return response;
|
|
860
|
-
}
|
|
861
|
-
};
|
|
885
|
+
function extractUserInfoFromToken(token) {
|
|
886
|
+
try {
|
|
887
|
+
const payload = decodeJwt(token);
|
|
888
|
+
return {
|
|
889
|
+
id: payload.ulid,
|
|
890
|
+
email: payload.email,
|
|
891
|
+
phone: payload.phone,
|
|
892
|
+
username: payload.username,
|
|
893
|
+
firstName: payload.first_name,
|
|
894
|
+
lastName: payload.last_name,
|
|
895
|
+
storeId: payload.store_id,
|
|
896
|
+
isLoggedIn: payload.is_logged_in,
|
|
897
|
+
isAnonymous: !payload.is_logged_in,
|
|
898
|
+
customerId: payload.customer_id,
|
|
899
|
+
customerGroupId: payload.customer_group_id,
|
|
900
|
+
anonymousId: payload.anonymous_id,
|
|
901
|
+
tokenExpiry: /* @__PURE__ */ new Date(payload.exp * 1e3),
|
|
902
|
+
tokenIssuedAt: /* @__PURE__ */ new Date(payload.iat * 1e3)
|
|
903
|
+
};
|
|
904
|
+
} catch (error) {
|
|
905
|
+
console.warn("Failed to decode JWT token:", error);
|
|
906
|
+
return null;
|
|
907
|
+
}
|
|
862
908
|
}
|
|
863
909
|
/**
|
|
864
|
-
*
|
|
910
|
+
* Check if a JWT token is expired
|
|
911
|
+
*
|
|
912
|
+
* @param token - The JWT token to check
|
|
913
|
+
* @param bufferSeconds - Buffer time in seconds (default: 30)
|
|
914
|
+
* @returns True if token is expired or will expire within buffer time
|
|
915
|
+
*/
|
|
916
|
+
function isTokenExpired(token, bufferSeconds = 30) {
|
|
917
|
+
try {
|
|
918
|
+
const payload = decodeJwt(token);
|
|
919
|
+
if (!payload.exp) return true;
|
|
920
|
+
const currentTime = Math.floor(Date.now() / 1e3);
|
|
921
|
+
const expiryTime = payload.exp;
|
|
922
|
+
return currentTime >= expiryTime - bufferSeconds;
|
|
923
|
+
} catch (error) {
|
|
924
|
+
console.warn("Failed to decode JWT token:", error);
|
|
925
|
+
return true;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* Get the user ID from a JWT token
|
|
930
|
+
*
|
|
931
|
+
* @param token - The JWT token
|
|
932
|
+
* @returns User ID (ulid) or null if token is invalid
|
|
933
|
+
*/
|
|
934
|
+
function getUserIdFromToken(token) {
|
|
935
|
+
const userInfo = extractUserInfoFromToken(token);
|
|
936
|
+
return userInfo?.id || null;
|
|
937
|
+
}
|
|
938
|
+
/**
|
|
939
|
+
* Check if user is logged in based on JWT token
|
|
940
|
+
*
|
|
941
|
+
* @param token - The JWT token
|
|
942
|
+
* @returns True if user is logged in, false otherwise
|
|
943
|
+
*/
|
|
944
|
+
function isUserLoggedIn(token) {
|
|
945
|
+
const userInfo = extractUserInfoFromToken(token);
|
|
946
|
+
return userInfo?.isLoggedIn || false;
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* Check if user is anonymous based on JWT token
|
|
950
|
+
*
|
|
951
|
+
* @param token - The JWT token
|
|
952
|
+
* @returns True if user is anonymous, false otherwise
|
|
953
|
+
*/
|
|
954
|
+
function isUserAnonymous(token) {
|
|
955
|
+
const userInfo = extractUserInfoFromToken(token);
|
|
956
|
+
return userInfo?.isAnonymous || true;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
//#endregion
|
|
960
|
+
//#region src/lib/auth-utils.ts
|
|
961
|
+
/**
|
|
962
|
+
* Check if a URL path is an auth endpoint that should use API key
|
|
963
|
+
*/
|
|
964
|
+
function isAnonymousAuthEndpoint(pathname) {
|
|
965
|
+
return pathname.endsWith("/auth/anonymous");
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Check if a URL path is a login/register endpoint that returns tokens
|
|
969
|
+
*/
|
|
970
|
+
function isTokenReturningEndpoint(pathname) {
|
|
971
|
+
const tokenEndpoints = [
|
|
972
|
+
"/auth/login/password",
|
|
973
|
+
"/auth/register/phone",
|
|
974
|
+
"/auth/register/email",
|
|
975
|
+
"/auth/verify-otp",
|
|
976
|
+
"/auth/refresh-token"
|
|
977
|
+
];
|
|
978
|
+
return tokenEndpoints.some((endpoint) => pathname.endsWith(endpoint));
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* Check if a URL path is the logout endpoint
|
|
865
982
|
*/
|
|
866
|
-
function
|
|
867
|
-
|
|
868
|
-
return createAuthMiddleware({
|
|
869
|
-
tokenStorage,
|
|
870
|
-
apiKey: options.apiKey,
|
|
871
|
-
baseUrl: options.baseUrl,
|
|
872
|
-
onTokensUpdated: options.onTokensUpdated,
|
|
873
|
-
onTokensCleared: options.onTokensCleared
|
|
874
|
-
});
|
|
983
|
+
function isLogoutEndpoint(pathname) {
|
|
984
|
+
return pathname.endsWith("/auth/logout");
|
|
875
985
|
}
|
|
876
986
|
|
|
877
987
|
//#endregion
|
|
878
|
-
//#region src/lib/
|
|
988
|
+
//#region src/lib/middleware.ts
|
|
879
989
|
/**
|
|
880
|
-
*
|
|
990
|
+
* Simple in-memory token storage implementation
|
|
881
991
|
*/
|
|
882
|
-
var
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
return Object.fromEntries(response.headers.entries());
|
|
888
|
-
}
|
|
889
|
-
/**
|
|
890
|
-
* Get specific header value
|
|
891
|
-
*/
|
|
892
|
-
static getHeader(response, name) {
|
|
893
|
-
return response.headers.get(name);
|
|
894
|
-
}
|
|
895
|
-
/**
|
|
896
|
-
* Check if response was successful
|
|
897
|
-
*/
|
|
898
|
-
static isSuccess(response) {
|
|
899
|
-
return response.ok;
|
|
992
|
+
var MemoryTokenStorage = class {
|
|
993
|
+
accessToken = null;
|
|
994
|
+
refreshToken = null;
|
|
995
|
+
async getAccessToken() {
|
|
996
|
+
return this.accessToken;
|
|
900
997
|
}
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
*/
|
|
904
|
-
static getMetadata(response) {
|
|
905
|
-
return {
|
|
906
|
-
status: response.status,
|
|
907
|
-
statusText: response.statusText,
|
|
908
|
-
ok: response.ok,
|
|
909
|
-
url: response.url,
|
|
910
|
-
redirected: response.redirected,
|
|
911
|
-
type: response.type,
|
|
912
|
-
headers: Object.fromEntries(response.headers.entries())
|
|
913
|
-
};
|
|
998
|
+
async setAccessToken(token) {
|
|
999
|
+
this.accessToken = token;
|
|
914
1000
|
}
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
* Note: This can only be called once per response
|
|
918
|
-
*/
|
|
919
|
-
static async getText(response) {
|
|
920
|
-
const cloned = response.clone();
|
|
921
|
-
return await cloned.text();
|
|
1001
|
+
async getRefreshToken() {
|
|
1002
|
+
return this.refreshToken;
|
|
922
1003
|
}
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
* Note: This can only be called once per response
|
|
926
|
-
*/
|
|
927
|
-
static async getJSON(response) {
|
|
928
|
-
const cloned = response.clone();
|
|
929
|
-
return await cloned.json();
|
|
1004
|
+
async setRefreshToken(token) {
|
|
1005
|
+
this.refreshToken = token;
|
|
930
1006
|
}
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
static format(response) {
|
|
935
|
-
const metadata = this.getMetadata(response);
|
|
936
|
-
return `${metadata.status} ${metadata.statusText} - ${metadata.url}`;
|
|
1007
|
+
async clearTokens() {
|
|
1008
|
+
this.accessToken = null;
|
|
1009
|
+
this.refreshToken = null;
|
|
937
1010
|
}
|
|
938
1011
|
};
|
|
939
1012
|
/**
|
|
940
|
-
*
|
|
1013
|
+
* Browser localStorage token storage implementation
|
|
941
1014
|
*/
|
|
942
|
-
var
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
constructor(
|
|
946
|
-
this.
|
|
947
|
-
|
|
948
|
-
if (data) console.log(data);
|
|
949
|
-
});
|
|
1015
|
+
var BrowserTokenStorage = class {
|
|
1016
|
+
accessTokenKey;
|
|
1017
|
+
refreshTokenKey;
|
|
1018
|
+
constructor(prefix = "storefront_") {
|
|
1019
|
+
this.accessTokenKey = `${prefix}access_token`;
|
|
1020
|
+
this.refreshTokenKey = `${prefix}refresh_token`;
|
|
950
1021
|
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
logRequest(request, requestBody) {
|
|
955
|
-
this.logger("info", "API Request Debug Info", {
|
|
956
|
-
method: request.method,
|
|
957
|
-
url: request.url,
|
|
958
|
-
headers: Object.fromEntries(request.headers.entries()),
|
|
959
|
-
body: requestBody,
|
|
960
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
961
|
-
});
|
|
1022
|
+
async getAccessToken() {
|
|
1023
|
+
if (typeof localStorage === "undefined") return null;
|
|
1024
|
+
return localStorage.getItem(this.accessTokenKey);
|
|
962
1025
|
}
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
*/
|
|
966
|
-
async logResponse(response, responseBody) {
|
|
967
|
-
if (responseBody && typeof responseBody === "string") this.responseTextCache.set(response.url, responseBody);
|
|
968
|
-
this.logger("info", "API Response Debug Info", {
|
|
969
|
-
url: response.url,
|
|
970
|
-
status: response.status,
|
|
971
|
-
statusText: response.statusText,
|
|
972
|
-
ok: response.ok,
|
|
973
|
-
headers: Object.fromEntries(response.headers.entries()),
|
|
974
|
-
redirected: response.redirected,
|
|
975
|
-
type: response.type,
|
|
976
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
977
|
-
});
|
|
978
|
-
if (responseBody) this.logger("info", "API Response Data", {
|
|
979
|
-
data: responseBody,
|
|
980
|
-
contentType: response.headers.get("content-type"),
|
|
981
|
-
contentLength: response.headers.get("content-length")
|
|
982
|
-
});
|
|
1026
|
+
async setAccessToken(token) {
|
|
1027
|
+
if (typeof localStorage !== "undefined") localStorage.setItem(this.accessTokenKey, token);
|
|
983
1028
|
}
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
logError(message, error) {
|
|
988
|
-
this.logger("error", message, error);
|
|
1029
|
+
async getRefreshToken() {
|
|
1030
|
+
if (typeof localStorage === "undefined") return null;
|
|
1031
|
+
return localStorage.getItem(this.refreshTokenKey);
|
|
989
1032
|
}
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
*/
|
|
993
|
-
getCachedResponseText(url) {
|
|
994
|
-
return this.responseTextCache.get(url) || null;
|
|
1033
|
+
async setRefreshToken(token) {
|
|
1034
|
+
if (typeof localStorage !== "undefined") localStorage.setItem(this.refreshTokenKey, token);
|
|
995
1035
|
}
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1036
|
+
async clearTokens() {
|
|
1037
|
+
if (typeof localStorage !== "undefined") {
|
|
1038
|
+
localStorage.removeItem(this.accessTokenKey);
|
|
1039
|
+
localStorage.removeItem(this.refreshTokenKey);
|
|
1040
|
+
}
|
|
1001
1041
|
}
|
|
1002
1042
|
};
|
|
1003
1043
|
/**
|
|
1004
|
-
*
|
|
1044
|
+
* Cookie-based token storage implementation
|
|
1005
1045
|
*/
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1046
|
+
var CookieTokenStorage = class {
|
|
1047
|
+
accessTokenKey;
|
|
1048
|
+
refreshTokenKey;
|
|
1049
|
+
options;
|
|
1050
|
+
constructor(options = {}) {
|
|
1051
|
+
const prefix = options.prefix || "storefront_";
|
|
1052
|
+
this.accessTokenKey = `${prefix}access_token`;
|
|
1053
|
+
this.refreshTokenKey = `${prefix}refresh_token`;
|
|
1054
|
+
this.options = {
|
|
1055
|
+
maxAge: options.maxAge || 10080 * 60,
|
|
1056
|
+
path: options.path || "/",
|
|
1057
|
+
domain: options.domain,
|
|
1058
|
+
secure: options.secure ?? (typeof window !== "undefined" && window.location?.protocol === "https:"),
|
|
1059
|
+
sameSite: options.sameSite || "Lax",
|
|
1060
|
+
httpOnly: false
|
|
1061
|
+
};
|
|
1017
1062
|
}
|
|
1018
|
-
|
|
1063
|
+
async getAccessToken() {
|
|
1064
|
+
return this.getCookie(this.accessTokenKey);
|
|
1065
|
+
}
|
|
1066
|
+
async setAccessToken(token) {
|
|
1067
|
+
this.setCookie(this.accessTokenKey, token);
|
|
1068
|
+
}
|
|
1069
|
+
async getRefreshToken() {
|
|
1070
|
+
return this.getCookie(this.refreshTokenKey);
|
|
1071
|
+
}
|
|
1072
|
+
async setRefreshToken(token) {
|
|
1073
|
+
this.setCookie(this.refreshTokenKey, token);
|
|
1074
|
+
}
|
|
1075
|
+
async clearTokens() {
|
|
1076
|
+
this.deleteCookie(this.accessTokenKey);
|
|
1077
|
+
this.deleteCookie(this.refreshTokenKey);
|
|
1078
|
+
}
|
|
1079
|
+
getCookie(name) {
|
|
1080
|
+
if (typeof document === "undefined") return null;
|
|
1081
|
+
const value = `; ${document.cookie}`;
|
|
1082
|
+
const parts = value.split(`; ${name}=`);
|
|
1083
|
+
if (parts.length === 2) {
|
|
1084
|
+
const cookieValue = parts.pop()?.split(";").shift();
|
|
1085
|
+
return cookieValue ? decodeURIComponent(cookieValue) : null;
|
|
1086
|
+
}
|
|
1087
|
+
return null;
|
|
1088
|
+
}
|
|
1089
|
+
setCookie(name, value) {
|
|
1090
|
+
if (typeof document === "undefined") return;
|
|
1091
|
+
const encodedValue = encodeURIComponent(value);
|
|
1092
|
+
let cookieString = `${name}=${encodedValue}`;
|
|
1093
|
+
if (this.options.maxAge) cookieString += `; Max-Age=${this.options.maxAge}`;
|
|
1094
|
+
if (this.options.path) cookieString += `; Path=${this.options.path}`;
|
|
1095
|
+
if (this.options.domain) cookieString += `; Domain=${this.options.domain}`;
|
|
1096
|
+
if (this.options.secure) cookieString += `; Secure`;
|
|
1097
|
+
if (this.options.sameSite) cookieString += `; SameSite=${this.options.sameSite}`;
|
|
1098
|
+
document.cookie = cookieString;
|
|
1099
|
+
}
|
|
1100
|
+
deleteCookie(name) {
|
|
1101
|
+
if (typeof document === "undefined") return;
|
|
1102
|
+
let cookieString = `${name}=; Max-Age=0`;
|
|
1103
|
+
if (this.options.path) cookieString += `; Path=${this.options.path}`;
|
|
1104
|
+
if (this.options.domain) cookieString += `; Domain=${this.options.domain}`;
|
|
1105
|
+
document.cookie = cookieString;
|
|
1106
|
+
}
|
|
1107
|
+
};
|
|
1019
1108
|
/**
|
|
1020
|
-
* Create
|
|
1109
|
+
* Create authentication middleware for openapi-fetch
|
|
1021
1110
|
*/
|
|
1022
|
-
function
|
|
1023
|
-
|
|
1111
|
+
function createAuthMiddleware(config) {
|
|
1112
|
+
let isRefreshing = false;
|
|
1113
|
+
let refreshPromise = null;
|
|
1114
|
+
let hasAssessedTokens = false;
|
|
1115
|
+
const assessTokenStateOnce = async () => {
|
|
1116
|
+
if (hasAssessedTokens) return;
|
|
1117
|
+
hasAssessedTokens = true;
|
|
1118
|
+
try {
|
|
1119
|
+
const accessToken = await config.tokenStorage.getAccessToken();
|
|
1120
|
+
const refreshToken = await config.tokenStorage.getRefreshToken();
|
|
1121
|
+
if (!accessToken && refreshToken) {
|
|
1122
|
+
await config.tokenStorage.clearTokens();
|
|
1123
|
+
console.info("Cleaned up orphaned refresh token");
|
|
1124
|
+
}
|
|
1125
|
+
} catch (error) {
|
|
1126
|
+
console.warn("Token state assessment failed:", error);
|
|
1127
|
+
}
|
|
1128
|
+
};
|
|
1129
|
+
const refreshTokens = async () => {
|
|
1130
|
+
if (isRefreshing && refreshPromise) return refreshPromise;
|
|
1131
|
+
isRefreshing = true;
|
|
1132
|
+
refreshPromise = (async () => {
|
|
1133
|
+
try {
|
|
1134
|
+
const refreshToken = await config.tokenStorage.getRefreshToken();
|
|
1135
|
+
let newTokens;
|
|
1136
|
+
if (refreshToken && !isTokenExpired(refreshToken)) if (config.refreshTokenFn) newTokens = await config.refreshTokenFn(refreshToken);
|
|
1137
|
+
else {
|
|
1138
|
+
const response = await fetch(`${config.baseUrl}/auth/refresh-token`, {
|
|
1139
|
+
method: "POST",
|
|
1140
|
+
headers: { "Content-Type": "application/json" },
|
|
1141
|
+
body: JSON.stringify({ refresh_token: refreshToken })
|
|
1142
|
+
});
|
|
1143
|
+
if (!response.ok) throw new Error(`Token refresh failed: ${response.status}`);
|
|
1144
|
+
const data = await response.json();
|
|
1145
|
+
newTokens = data.content;
|
|
1146
|
+
}
|
|
1147
|
+
else {
|
|
1148
|
+
const currentAccessToken = await config.tokenStorage.getAccessToken();
|
|
1149
|
+
if (!currentAccessToken) throw new Error("No tokens available for refresh");
|
|
1150
|
+
const reason = refreshToken ? "refresh token expired" : "no refresh token available";
|
|
1151
|
+
const response = await fetch(`${config.baseUrl}/auth/anonymous`, {
|
|
1152
|
+
method: "POST",
|
|
1153
|
+
headers: {
|
|
1154
|
+
"Content-Type": "application/json",
|
|
1155
|
+
...config.apiKey && { "X-Api-Key": config.apiKey },
|
|
1156
|
+
Authorization: `Bearer ${currentAccessToken}`
|
|
1157
|
+
}
|
|
1158
|
+
});
|
|
1159
|
+
if (!response.ok) throw new Error(`Anonymous token fallback failed: ${response.status}`);
|
|
1160
|
+
const data = await response.json();
|
|
1161
|
+
newTokens = data.content;
|
|
1162
|
+
console.info(`Token refreshed via anonymous fallback (${reason}) - user may need to re-authenticate for privileged operations`);
|
|
1163
|
+
}
|
|
1164
|
+
await config.tokenStorage.setAccessToken(newTokens.access_token);
|
|
1165
|
+
await config.tokenStorage.setRefreshToken(newTokens.refresh_token);
|
|
1166
|
+
config.onTokensUpdated?.(newTokens.access_token, newTokens.refresh_token);
|
|
1167
|
+
} catch (error) {
|
|
1168
|
+
console.error("Token refresh failed:", error);
|
|
1169
|
+
await config.tokenStorage.clearTokens();
|
|
1170
|
+
config.onTokensCleared?.();
|
|
1171
|
+
throw error;
|
|
1172
|
+
} finally {
|
|
1173
|
+
isRefreshing = false;
|
|
1174
|
+
refreshPromise = null;
|
|
1175
|
+
}
|
|
1176
|
+
})();
|
|
1177
|
+
return refreshPromise;
|
|
1178
|
+
};
|
|
1024
1179
|
return {
|
|
1025
1180
|
async onRequest({ request }) {
|
|
1026
|
-
const
|
|
1027
|
-
|
|
1181
|
+
const pathname = getPathnameFromUrl(request.url);
|
|
1182
|
+
await assessTokenStateOnce();
|
|
1183
|
+
if (isAnonymousAuthEndpoint(pathname)) {
|
|
1184
|
+
if (config.apiKey) request.headers.set("X-Api-Key", config.apiKey);
|
|
1185
|
+
const existingToken = await config.tokenStorage.getAccessToken();
|
|
1186
|
+
if (existingToken && !isTokenExpired(existingToken) && isUserLoggedIn(existingToken)) return new Response(JSON.stringify({
|
|
1187
|
+
message: "Cannot create anonymous session while authenticated",
|
|
1188
|
+
success: false,
|
|
1189
|
+
code: "USER_ALREADY_AUTHENTICATED"
|
|
1190
|
+
}), {
|
|
1191
|
+
status: 400,
|
|
1192
|
+
headers: { "Content-Type": "application/json" }
|
|
1193
|
+
});
|
|
1194
|
+
if (existingToken) request.headers.set("Authorization", `Bearer ${existingToken}`);
|
|
1195
|
+
return request;
|
|
1196
|
+
}
|
|
1197
|
+
let accessToken = await config.tokenStorage.getAccessToken();
|
|
1198
|
+
if (!accessToken) try {
|
|
1199
|
+
const response = await fetch(`${config.baseUrl}/auth/anonymous`, {
|
|
1200
|
+
method: "POST",
|
|
1201
|
+
headers: {
|
|
1202
|
+
"Content-Type": "application/json",
|
|
1203
|
+
...config.apiKey && { "X-Api-Key": config.apiKey }
|
|
1204
|
+
}
|
|
1205
|
+
});
|
|
1206
|
+
if (response.ok) {
|
|
1207
|
+
const data = await response.json();
|
|
1208
|
+
const tokens = data.content;
|
|
1209
|
+
if (tokens?.access_token && tokens?.refresh_token) {
|
|
1210
|
+
await config.tokenStorage.setAccessToken(tokens.access_token);
|
|
1211
|
+
await config.tokenStorage.setRefreshToken(tokens.refresh_token);
|
|
1212
|
+
accessToken = tokens.access_token;
|
|
1213
|
+
config.onTokensUpdated?.(tokens.access_token, tokens.refresh_token);
|
|
1214
|
+
console.info("Automatically created anonymous session for first API request");
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
} catch (error) {
|
|
1218
|
+
console.warn("Failed to automatically create anonymous tokens:", error);
|
|
1219
|
+
}
|
|
1220
|
+
if (accessToken && isTokenExpired(accessToken)) try {
|
|
1221
|
+
await refreshTokens();
|
|
1222
|
+
accessToken = await config.tokenStorage.getAccessToken();
|
|
1223
|
+
} catch (error) {}
|
|
1224
|
+
if (accessToken) request.headers.set("Authorization", `Bearer ${accessToken}`);
|
|
1028
1225
|
return request;
|
|
1029
1226
|
},
|
|
1030
|
-
async onResponse({ response }) {
|
|
1031
|
-
const
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1227
|
+
async onResponse({ request, response }) {
|
|
1228
|
+
const pathname = getPathnameFromUrl(request.url);
|
|
1229
|
+
if (response.ok) {
|
|
1230
|
+
if (isTokenReturningEndpoint(pathname) || isAnonymousAuthEndpoint(pathname)) try {
|
|
1231
|
+
const data = await response.clone().json();
|
|
1232
|
+
const content = data.content;
|
|
1233
|
+
if (content?.access_token && content?.refresh_token) {
|
|
1234
|
+
await config.tokenStorage.setAccessToken(content.access_token);
|
|
1235
|
+
await config.tokenStorage.setRefreshToken(content.refresh_token);
|
|
1236
|
+
config.onTokensUpdated?.(content.access_token, content.refresh_token);
|
|
1237
|
+
}
|
|
1238
|
+
} catch (error) {
|
|
1239
|
+
console.warn("Failed to extract tokens from response:", error);
|
|
1240
|
+
}
|
|
1241
|
+
else if (isLogoutEndpoint(pathname)) {
|
|
1242
|
+
await config.tokenStorage.clearTokens();
|
|
1243
|
+
config.onTokensCleared?.();
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
if (response.status === 401 && !isAnonymousAuthEndpoint(pathname)) {
|
|
1247
|
+
const currentToken = await config.tokenStorage.getAccessToken();
|
|
1248
|
+
if (currentToken && isTokenExpired(currentToken, 0)) try {
|
|
1249
|
+
await refreshTokens();
|
|
1250
|
+
const newToken = await config.tokenStorage.getAccessToken();
|
|
1251
|
+
if (newToken) {
|
|
1252
|
+
const retryRequest = request.clone();
|
|
1253
|
+
retryRequest.headers.set("Authorization", `Bearer ${newToken}`);
|
|
1254
|
+
return fetch(retryRequest);
|
|
1255
|
+
}
|
|
1256
|
+
} catch (error) {
|
|
1257
|
+
console.warn("Token refresh failed on 401 response:", error);
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1039
1260
|
return response;
|
|
1040
|
-
},
|
|
1041
|
-
async onError({ error, request }) {
|
|
1042
|
-
debugLogger.logError("API Request Failed", {
|
|
1043
|
-
error: {
|
|
1044
|
-
name: error instanceof Error ? error.name : "Unknown",
|
|
1045
|
-
message: error instanceof Error ? error.message : String(error),
|
|
1046
|
-
stack: error instanceof Error ? error.stack : void 0
|
|
1047
|
-
},
|
|
1048
|
-
request: {
|
|
1049
|
-
method: request.method,
|
|
1050
|
-
url: request.url,
|
|
1051
|
-
headers: Object.fromEntries(request.headers.entries())
|
|
1052
|
-
},
|
|
1053
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1054
|
-
});
|
|
1055
|
-
throw error;
|
|
1056
1261
|
}
|
|
1057
1262
|
};
|
|
1058
1263
|
}
|
|
1059
|
-
|
|
1060
|
-
//#endregion
|
|
1061
|
-
//#region src/lib/header-utils.ts
|
|
1062
|
-
/**
|
|
1063
|
-
* Mapping of SDK header parameter names to actual HTTP header names
|
|
1064
|
-
* Only include headers that need transformation - others pass through as-is
|
|
1065
|
-
*/
|
|
1066
|
-
const HEADER_TRANSFORMATIONS = { customer_group_id: "x-customer-group-id" };
|
|
1067
|
-
/**
|
|
1068
|
-
* Transform SDK header parameters to actual HTTP header names
|
|
1069
|
-
* Headers not in the transformation map are passed through unchanged
|
|
1070
|
-
*
|
|
1071
|
-
* @param headers - Headers object with SDK parameter names
|
|
1072
|
-
* @returns Headers object with actual HTTP header names
|
|
1073
|
-
*/
|
|
1074
|
-
function transformHeaders(headers) {
|
|
1075
|
-
const transformed = {};
|
|
1076
|
-
for (const [key, value] of Object.entries(headers)) if (value !== void 0) {
|
|
1077
|
-
const headerName = HEADER_TRANSFORMATIONS[key] || key;
|
|
1078
|
-
transformed[headerName] = value;
|
|
1079
|
-
}
|
|
1080
|
-
return transformed;
|
|
1081
|
-
}
|
|
1082
1264
|
/**
|
|
1083
|
-
*
|
|
1084
|
-
* Method-level headers take precedence over default headers
|
|
1085
|
-
* Automatically transforms SDK parameter names to HTTP header names
|
|
1086
|
-
*
|
|
1087
|
-
* @param defaultHeaders - Default headers from SDK configuration
|
|
1088
|
-
* @param methodHeaders - Headers passed to the specific method call
|
|
1089
|
-
* @returns Merged headers object with proper HTTP header names
|
|
1265
|
+
* Helper function to create auth middleware with sensible defaults
|
|
1090
1266
|
*/
|
|
1091
|
-
function
|
|
1092
|
-
const
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
if (merged[key] === void 0) delete merged[key];
|
|
1267
|
+
function createDefaultAuthMiddleware(options) {
|
|
1268
|
+
const tokenStorage = options.tokenStorage || (typeof localStorage !== "undefined" ? new BrowserTokenStorage() : new MemoryTokenStorage());
|
|
1269
|
+
return createAuthMiddleware({
|
|
1270
|
+
tokenStorage,
|
|
1271
|
+
apiKey: options.apiKey,
|
|
1272
|
+
baseUrl: options.baseUrl,
|
|
1273
|
+
onTokensUpdated: options.onTokensUpdated,
|
|
1274
|
+
onTokensCleared: options.onTokensCleared
|
|
1100
1275
|
});
|
|
1101
|
-
return merged;
|
|
1102
1276
|
}
|
|
1103
1277
|
|
|
1104
1278
|
//#endregion
|
|
1105
|
-
//#region src/lib/
|
|
1279
|
+
//#region src/lib/url-utils.ts
|
|
1106
1280
|
/**
|
|
1107
|
-
*
|
|
1281
|
+
* URL utility functions for the Storefront SDK
|
|
1282
|
+
*/
|
|
1283
|
+
/**
|
|
1284
|
+
* Available API environments for Commerce Engine
|
|
1108
1285
|
*/
|
|
1109
1286
|
let Environment = /* @__PURE__ */ function(Environment$1) {
|
|
1110
1287
|
/**
|
|
@@ -1118,12 +1295,26 @@ let Environment = /* @__PURE__ */ function(Environment$1) {
|
|
|
1118
1295
|
return Environment$1;
|
|
1119
1296
|
}({});
|
|
1120
1297
|
/**
|
|
1121
|
-
*
|
|
1298
|
+
* Build base URL for Storefront API
|
|
1122
1299
|
*/
|
|
1123
|
-
|
|
1124
|
-
|
|
1300
|
+
function buildStorefrontURL(config) {
|
|
1301
|
+
if (config.baseUrl) return config.baseUrl;
|
|
1302
|
+
const env = config.environment || Environment.Production;
|
|
1303
|
+
switch (env) {
|
|
1304
|
+
case Environment.Staging: return `https://staging.api.commercengine.io/api/v1/${config.storeId}/storefront`;
|
|
1305
|
+
case Environment.Production:
|
|
1306
|
+
default: return `https://prod.api.commercengine.io/api/v1/${config.storeId}/storefront`;
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
//#endregion
|
|
1311
|
+
//#region src/lib/client.ts
|
|
1312
|
+
/**
|
|
1313
|
+
* Storefront API client that extends the generic BaseAPIClient
|
|
1314
|
+
* Adds Commerce Engine specific authentication and token management
|
|
1315
|
+
*/
|
|
1316
|
+
var StorefrontAPIClient = class extends BaseAPIClient {
|
|
1125
1317
|
config;
|
|
1126
|
-
baseUrl;
|
|
1127
1318
|
initializationPromise = null;
|
|
1128
1319
|
/**
|
|
1129
1320
|
* Create a new StorefrontAPIClient
|
|
@@ -1131,74 +1322,51 @@ var StorefrontAPIClient = class {
|
|
|
1131
1322
|
* @param config - Configuration for the API client
|
|
1132
1323
|
*/
|
|
1133
1324
|
constructor(config) {
|
|
1325
|
+
const baseUrl = buildStorefrontURL({
|
|
1326
|
+
storeId: config.storeId,
|
|
1327
|
+
environment: config.environment,
|
|
1328
|
+
baseUrl: config.baseUrl
|
|
1329
|
+
});
|
|
1330
|
+
const headerTransformations = { customer_group_id: "x-customer-group-id" };
|
|
1331
|
+
super({
|
|
1332
|
+
baseUrl,
|
|
1333
|
+
timeout: config.timeout,
|
|
1334
|
+
defaultHeaders: config.defaultHeaders,
|
|
1335
|
+
debug: config.debug,
|
|
1336
|
+
logger: config.logger
|
|
1337
|
+
}, baseUrl, headerTransformations);
|
|
1134
1338
|
this.config = { ...config };
|
|
1135
|
-
this.
|
|
1136
|
-
|
|
1137
|
-
|
|
1339
|
+
this.setupStorefrontAuth();
|
|
1340
|
+
}
|
|
1341
|
+
/**
|
|
1342
|
+
* Set up Storefront-specific authentication middleware
|
|
1343
|
+
*/
|
|
1344
|
+
setupStorefrontAuth() {
|
|
1345
|
+
const config = this.config;
|
|
1346
|
+
if (config.tokenStorage) {
|
|
1138
1347
|
const authMiddleware = createDefaultAuthMiddleware({
|
|
1139
|
-
apiKey:
|
|
1140
|
-
baseUrl: this.
|
|
1141
|
-
tokenStorage:
|
|
1142
|
-
onTokensUpdated:
|
|
1143
|
-
onTokensCleared:
|
|
1348
|
+
apiKey: config.apiKey,
|
|
1349
|
+
baseUrl: this.getBaseUrl(),
|
|
1350
|
+
tokenStorage: config.tokenStorage,
|
|
1351
|
+
onTokensUpdated: config.onTokensUpdated,
|
|
1352
|
+
onTokensCleared: config.onTokensCleared
|
|
1144
1353
|
});
|
|
1145
1354
|
this.client.use(authMiddleware);
|
|
1146
|
-
if (
|
|
1147
|
-
this.initializationPromise = this.initializeTokens(
|
|
1148
|
-
|
|
1149
|
-
|
|
1355
|
+
if (config.accessToken) {
|
|
1356
|
+
this.initializationPromise = this.initializeTokens(config.accessToken, config.refreshToken);
|
|
1357
|
+
config.accessToken = void 0;
|
|
1358
|
+
config.refreshToken = void 0;
|
|
1150
1359
|
}
|
|
1151
1360
|
} else this.client.use({ onRequest: async ({ request }) => {
|
|
1152
1361
|
const pathname = getPathnameFromUrl(request.url);
|
|
1153
1362
|
if (isAnonymousAuthEndpoint(pathname)) {
|
|
1154
|
-
if (
|
|
1155
|
-
if (
|
|
1363
|
+
if (config.apiKey) request.headers.set("X-Api-Key", config.apiKey);
|
|
1364
|
+
if (config.accessToken) request.headers.set("Authorization", `Bearer ${config.accessToken}`);
|
|
1156
1365
|
return request;
|
|
1157
1366
|
}
|
|
1158
|
-
if (
|
|
1367
|
+
if (config.accessToken) request.headers.set("Authorization", `Bearer ${config.accessToken}`);
|
|
1159
1368
|
return request;
|
|
1160
1369
|
} });
|
|
1161
|
-
if (this.config.timeout) this.setupTimeoutMiddleware();
|
|
1162
|
-
if (this.config.debug) {
|
|
1163
|
-
const debugMiddleware = createDebugMiddleware(this.config.logger);
|
|
1164
|
-
this.client.use(debugMiddleware);
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1167
|
-
/**
|
|
1168
|
-
* Set up timeout middleware
|
|
1169
|
-
*/
|
|
1170
|
-
setupTimeoutMiddleware() {
|
|
1171
|
-
this.client.use({ onRequest: async ({ request }) => {
|
|
1172
|
-
const controller = new AbortController();
|
|
1173
|
-
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
1174
|
-
if (request.signal) request.signal.addEventListener("abort", () => controller.abort());
|
|
1175
|
-
const newRequest = new Request(request, { signal: controller.signal });
|
|
1176
|
-
controller.signal.addEventListener("abort", () => clearTimeout(timeoutId));
|
|
1177
|
-
return newRequest;
|
|
1178
|
-
} });
|
|
1179
|
-
}
|
|
1180
|
-
/**
|
|
1181
|
-
* Constructs the base URL from the configuration
|
|
1182
|
-
*
|
|
1183
|
-
* @param config - The client configuration
|
|
1184
|
-
* @returns The constructed base URL
|
|
1185
|
-
*/
|
|
1186
|
-
getBaseUrlFromConfig(config) {
|
|
1187
|
-
if (config.baseUrl) return config.baseUrl;
|
|
1188
|
-
const env = config.environment || Environment.Production;
|
|
1189
|
-
switch (env) {
|
|
1190
|
-
case Environment.Staging: return `https://staging.api.commercengine.io/api/v1/${config.storeId}/storefront`;
|
|
1191
|
-
case Environment.Production:
|
|
1192
|
-
default: return `https://prod.api.commercengine.io/api/v1/${config.storeId}/storefront`;
|
|
1193
|
-
}
|
|
1194
|
-
}
|
|
1195
|
-
/**
|
|
1196
|
-
* Get the base URL of the API
|
|
1197
|
-
*
|
|
1198
|
-
* @returns The base URL of the API
|
|
1199
|
-
*/
|
|
1200
|
-
getBaseUrl() {
|
|
1201
|
-
return this.baseUrl;
|
|
1202
1370
|
}
|
|
1203
1371
|
/**
|
|
1204
1372
|
* Get the authorization header value
|
|
@@ -1260,48 +1428,6 @@ var StorefrontAPIClient = class {
|
|
|
1260
1428
|
this.config.apiKey = void 0;
|
|
1261
1429
|
}
|
|
1262
1430
|
/**
|
|
1263
|
-
* Execute a request and handle the response
|
|
1264
|
-
*
|
|
1265
|
-
* @param apiCall - Function that executes the API request
|
|
1266
|
-
* @returns Promise with the API response
|
|
1267
|
-
*/
|
|
1268
|
-
async executeRequest(apiCall) {
|
|
1269
|
-
try {
|
|
1270
|
-
const { data, error, response } = await apiCall();
|
|
1271
|
-
if (error) return {
|
|
1272
|
-
data: null,
|
|
1273
|
-
error,
|
|
1274
|
-
response
|
|
1275
|
-
};
|
|
1276
|
-
if (data && data.content !== void 0) return {
|
|
1277
|
-
data: data.content,
|
|
1278
|
-
error: null,
|
|
1279
|
-
response
|
|
1280
|
-
};
|
|
1281
|
-
return {
|
|
1282
|
-
data,
|
|
1283
|
-
error: null,
|
|
1284
|
-
response
|
|
1285
|
-
};
|
|
1286
|
-
} catch (err) {
|
|
1287
|
-
const mockResponse = new Response(null, {
|
|
1288
|
-
status: 0,
|
|
1289
|
-
statusText: "Network Error"
|
|
1290
|
-
});
|
|
1291
|
-
const errorResult = {
|
|
1292
|
-
data: null,
|
|
1293
|
-
error: {
|
|
1294
|
-
success: false,
|
|
1295
|
-
code: "NETWORK_ERROR",
|
|
1296
|
-
message: "Network error occurred",
|
|
1297
|
-
error: err
|
|
1298
|
-
},
|
|
1299
|
-
response: mockResponse
|
|
1300
|
-
};
|
|
1301
|
-
return errorResult;
|
|
1302
|
-
}
|
|
1303
|
-
}
|
|
1304
|
-
/**
|
|
1305
1431
|
* Initialize tokens in storage (private helper method)
|
|
1306
1432
|
*/
|
|
1307
1433
|
async initializeTokens(accessToken, refreshToken) {
|
|
@@ -1314,16 +1440,6 @@ var StorefrontAPIClient = class {
|
|
|
1314
1440
|
console.warn("Failed to initialize tokens in storage:", error);
|
|
1315
1441
|
}
|
|
1316
1442
|
}
|
|
1317
|
-
/**
|
|
1318
|
-
* Merge default headers with method-level headers
|
|
1319
|
-
* Method-level headers take precedence over default headers
|
|
1320
|
-
*
|
|
1321
|
-
* @param methodHeaders - Headers passed to the specific method call
|
|
1322
|
-
* @returns Merged headers object with proper HTTP header names
|
|
1323
|
-
*/
|
|
1324
|
-
mergeHeaders(methodHeaders) {
|
|
1325
|
-
return mergeHeaders(this.config.defaultHeaders, methodHeaders);
|
|
1326
|
-
}
|
|
1327
1443
|
};
|
|
1328
1444
|
|
|
1329
1445
|
//#endregion
|
|
@@ -4108,11 +4224,16 @@ var StorefrontSDK = class {
|
|
|
4108
4224
|
*/
|
|
4109
4225
|
store;
|
|
4110
4226
|
/**
|
|
4227
|
+
* Centrally stored default headers for consistency
|
|
4228
|
+
*/
|
|
4229
|
+
defaultHeaders;
|
|
4230
|
+
/**
|
|
4111
4231
|
* Create a new StorefrontSDK instance
|
|
4112
4232
|
*
|
|
4113
4233
|
* @param options - Configuration options for the SDK
|
|
4114
4234
|
*/
|
|
4115
4235
|
constructor(options) {
|
|
4236
|
+
this.defaultHeaders = options.defaultHeaders;
|
|
4116
4237
|
const config = {
|
|
4117
4238
|
storeId: options.storeId,
|
|
4118
4239
|
environment: options.environment,
|
|
@@ -4272,26 +4393,23 @@ var StorefrontSDK = class {
|
|
|
4272
4393
|
* @param headers - Default headers to set (only supported headers allowed)
|
|
4273
4394
|
*/
|
|
4274
4395
|
setDefaultHeaders(headers) {
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
this.
|
|
4280
|
-
this.
|
|
4281
|
-
this.
|
|
4282
|
-
this.
|
|
4283
|
-
this.
|
|
4284
|
-
this.shipping["config"] = newConfig;
|
|
4285
|
-
this.order["config"] = newConfig;
|
|
4286
|
-
this.store["config"] = newConfig;
|
|
4396
|
+
this.defaultHeaders = headers;
|
|
4397
|
+
this.catalog.setDefaultHeaders(headers);
|
|
4398
|
+
this.cart.setDefaultHeaders(headers);
|
|
4399
|
+
this.auth.setDefaultHeaders(headers);
|
|
4400
|
+
this.customer.setDefaultHeaders(headers);
|
|
4401
|
+
this.helpers.setDefaultHeaders(headers);
|
|
4402
|
+
this.shipping.setDefaultHeaders(headers);
|
|
4403
|
+
this.order.setDefaultHeaders(headers);
|
|
4404
|
+
this.store.setDefaultHeaders(headers);
|
|
4287
4405
|
}
|
|
4288
4406
|
/**
|
|
4289
4407
|
* Get current default headers
|
|
4290
4408
|
*
|
|
4291
|
-
* @returns Current default headers
|
|
4409
|
+
* @returns Current default headers from central storage (always consistent)
|
|
4292
4410
|
*/
|
|
4293
4411
|
getDefaultHeaders() {
|
|
4294
|
-
return this.
|
|
4412
|
+
return this.defaultHeaders;
|
|
4295
4413
|
}
|
|
4296
4414
|
};
|
|
4297
4415
|
var src_default = StorefrontSDK;
|