@commercengine/storefront-sdk 0.3.0 → 0.3.2
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 +90 -39
- package/dist/index.js +156 -127
- package/dist/lib/auth-utils.d.ts +16 -0
- package/dist/lib/auth-utils.js +38 -0
- package/dist/lib/auth.d.ts +105 -195
- package/dist/lib/auth.js +221 -653
- package/dist/lib/cart.d.ts +77 -80
- package/dist/lib/cart.js +183 -221
- package/dist/lib/catalog.d.ts +36 -69
- package/dist/lib/catalog.js +109 -118
- package/dist/lib/client.d.ts +37 -84
- package/dist/lib/client.js +148 -173
- package/dist/lib/customer.d.ts +87 -0
- package/dist/lib/customer.js +153 -0
- package/dist/lib/helper.d.ts +27 -0
- package/dist/lib/helper.js +40 -0
- package/dist/lib/jwt-utils.d.ts +75 -0
- package/dist/lib/jwt-utils.js +84 -0
- package/dist/lib/middleware.d.ts +83 -0
- package/dist/lib/middleware.js +248 -0
- package/dist/lib/order.d.ts +72 -0
- package/dist/lib/order.js +125 -0
- package/dist/lib/shipping.d.ts +14 -0
- package/dist/lib/shipping.js +17 -0
- package/dist/types/storefront-api-types.d.ts +359 -0
- package/dist/types/storefront-api-types.js +3 -0
- package/dist/types/storefront.d.ts +7976 -7369
- package/package.json +21 -12
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { StorefrontAPIClient } from "./client";
|
|
2
|
+
/**
|
|
3
|
+
* Client for interacting with helper endpoints
|
|
4
|
+
*/
|
|
5
|
+
export class HelpersClient extends StorefrontAPIClient {
|
|
6
|
+
/**
|
|
7
|
+
* Get a list of countries
|
|
8
|
+
*
|
|
9
|
+
* @returns Promise with countries
|
|
10
|
+
*/
|
|
11
|
+
async listCountries() {
|
|
12
|
+
return this.executeRequest(() => this.client.GET("/common/countries", {}));
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* - Get a list of states for a country
|
|
16
|
+
*
|
|
17
|
+
* @param pathParams - Path parameters
|
|
18
|
+
* @returns Promise with states
|
|
19
|
+
*/
|
|
20
|
+
async listCountryStates(pathParams) {
|
|
21
|
+
return this.executeRequest(() => this.client.GET("/common/countries/{country_iso_code}/states", {
|
|
22
|
+
params: {
|
|
23
|
+
path: pathParams,
|
|
24
|
+
},
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Get pincodes for a country
|
|
29
|
+
*
|
|
30
|
+
* @param pathParams - Path parameters
|
|
31
|
+
* @returns Promise with pincodes
|
|
32
|
+
*/
|
|
33
|
+
async listCountryPincodes(pathParams) {
|
|
34
|
+
return this.executeRequest(() => this.client.GET("/common/countries/{country_iso_code}/pincodes", {
|
|
35
|
+
params: {
|
|
36
|
+
path: pathParams,
|
|
37
|
+
},
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWT payload structure for storefront tokens
|
|
3
|
+
*/
|
|
4
|
+
export interface JwtPayload {
|
|
5
|
+
token_type: string;
|
|
6
|
+
exp: number;
|
|
7
|
+
iat: number;
|
|
8
|
+
jti: string;
|
|
9
|
+
ulid: string;
|
|
10
|
+
email: string | null;
|
|
11
|
+
phone: string | null;
|
|
12
|
+
username: string;
|
|
13
|
+
first_name: string | null;
|
|
14
|
+
last_name: string | null;
|
|
15
|
+
store_id: string;
|
|
16
|
+
is_logged_in: boolean;
|
|
17
|
+
customer_id: string | null;
|
|
18
|
+
customer_group_id: string | null;
|
|
19
|
+
anonymous_id: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* User information extracted from JWT token
|
|
23
|
+
*/
|
|
24
|
+
export interface UserInfo {
|
|
25
|
+
id: string;
|
|
26
|
+
email: string | null;
|
|
27
|
+
phone: string | null;
|
|
28
|
+
username: string;
|
|
29
|
+
firstName: string | null;
|
|
30
|
+
lastName: string | null;
|
|
31
|
+
storeId: string;
|
|
32
|
+
isLoggedIn: boolean;
|
|
33
|
+
isAnonymous: boolean;
|
|
34
|
+
customerId: string | null;
|
|
35
|
+
customerGroupId: string | null;
|
|
36
|
+
anonymousId: string;
|
|
37
|
+
tokenExpiry: Date;
|
|
38
|
+
tokenIssuedAt: Date;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Decode and extract user information from a JWT token
|
|
42
|
+
*
|
|
43
|
+
* @param token - The JWT token to decode
|
|
44
|
+
* @returns User information or null if token is invalid
|
|
45
|
+
*/
|
|
46
|
+
export declare function extractUserInfoFromToken(token: string): UserInfo | null;
|
|
47
|
+
/**
|
|
48
|
+
* Check if a JWT token is expired
|
|
49
|
+
*
|
|
50
|
+
* @param token - The JWT token to check
|
|
51
|
+
* @param bufferSeconds - Buffer time in seconds (default: 30)
|
|
52
|
+
* @returns True if token is expired or will expire within buffer time
|
|
53
|
+
*/
|
|
54
|
+
export declare function isTokenExpired(token: string, bufferSeconds?: number): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Get the user ID from a JWT token
|
|
57
|
+
*
|
|
58
|
+
* @param token - The JWT token
|
|
59
|
+
* @returns User ID (ulid) or null if token is invalid
|
|
60
|
+
*/
|
|
61
|
+
export declare function getUserIdFromToken(token: string): string | null;
|
|
62
|
+
/**
|
|
63
|
+
* Check if user is logged in based on JWT token
|
|
64
|
+
*
|
|
65
|
+
* @param token - The JWT token
|
|
66
|
+
* @returns True if user is logged in, false otherwise
|
|
67
|
+
*/
|
|
68
|
+
export declare function isUserLoggedIn(token: string): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Check if user is anonymous based on JWT token
|
|
71
|
+
*
|
|
72
|
+
* @param token - The JWT token
|
|
73
|
+
* @returns True if user is anonymous, false otherwise
|
|
74
|
+
*/
|
|
75
|
+
export declare function isUserAnonymous(token: string): boolean;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { decodeJwt } from "jose";
|
|
2
|
+
/**
|
|
3
|
+
* Decode and extract user information from a JWT token
|
|
4
|
+
*
|
|
5
|
+
* @param token - The JWT token to decode
|
|
6
|
+
* @returns User information or null if token is invalid
|
|
7
|
+
*/
|
|
8
|
+
export function extractUserInfoFromToken(token) {
|
|
9
|
+
try {
|
|
10
|
+
const payload = decodeJwt(token);
|
|
11
|
+
return {
|
|
12
|
+
id: payload.ulid,
|
|
13
|
+
email: payload.email,
|
|
14
|
+
phone: payload.phone,
|
|
15
|
+
username: payload.username,
|
|
16
|
+
firstName: payload.first_name,
|
|
17
|
+
lastName: payload.last_name,
|
|
18
|
+
storeId: payload.store_id,
|
|
19
|
+
isLoggedIn: payload.is_logged_in,
|
|
20
|
+
isAnonymous: !payload.is_logged_in,
|
|
21
|
+
customerId: payload.customer_id,
|
|
22
|
+
customerGroupId: payload.customer_group_id,
|
|
23
|
+
anonymousId: payload.anonymous_id,
|
|
24
|
+
tokenExpiry: new Date(payload.exp * 1000),
|
|
25
|
+
tokenIssuedAt: new Date(payload.iat * 1000),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
console.warn("Failed to decode JWT token:", error);
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Check if a JWT token is expired
|
|
35
|
+
*
|
|
36
|
+
* @param token - The JWT token to check
|
|
37
|
+
* @param bufferSeconds - Buffer time in seconds (default: 30)
|
|
38
|
+
* @returns True if token is expired or will expire within buffer time
|
|
39
|
+
*/
|
|
40
|
+
export function isTokenExpired(token, bufferSeconds = 30) {
|
|
41
|
+
try {
|
|
42
|
+
const payload = decodeJwt(token);
|
|
43
|
+
if (!payload.exp)
|
|
44
|
+
return true;
|
|
45
|
+
const currentTime = Math.floor(Date.now() / 1000);
|
|
46
|
+
const expiryTime = payload.exp;
|
|
47
|
+
// Consider token expired if it expires within the buffer time
|
|
48
|
+
return currentTime >= (expiryTime - bufferSeconds);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
console.warn("Failed to decode JWT token:", error);
|
|
52
|
+
return true; // Treat invalid tokens as expired
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Get the user ID from a JWT token
|
|
57
|
+
*
|
|
58
|
+
* @param token - The JWT token
|
|
59
|
+
* @returns User ID (ulid) or null if token is invalid
|
|
60
|
+
*/
|
|
61
|
+
export function getUserIdFromToken(token) {
|
|
62
|
+
const userInfo = extractUserInfoFromToken(token);
|
|
63
|
+
return userInfo?.id || null;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Check if user is logged in based on JWT token
|
|
67
|
+
*
|
|
68
|
+
* @param token - The JWT token
|
|
69
|
+
* @returns True if user is logged in, false otherwise
|
|
70
|
+
*/
|
|
71
|
+
export function isUserLoggedIn(token) {
|
|
72
|
+
const userInfo = extractUserInfoFromToken(token);
|
|
73
|
+
return userInfo?.isLoggedIn || false;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Check if user is anonymous based on JWT token
|
|
77
|
+
*
|
|
78
|
+
* @param token - The JWT token
|
|
79
|
+
* @returns True if user is anonymous, false otherwise
|
|
80
|
+
*/
|
|
81
|
+
export function isUserAnonymous(token) {
|
|
82
|
+
const userInfo = extractUserInfoFromToken(token);
|
|
83
|
+
return userInfo?.isAnonymous || true;
|
|
84
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { Middleware } from "openapi-fetch";
|
|
2
|
+
/**
|
|
3
|
+
* Token storage interface for the auth middleware
|
|
4
|
+
*/
|
|
5
|
+
export interface TokenStorage {
|
|
6
|
+
getAccessToken(): Promise<string | null>;
|
|
7
|
+
setAccessToken(token: string): Promise<void>;
|
|
8
|
+
getRefreshToken(): Promise<string | null>;
|
|
9
|
+
setRefreshToken(token: string): Promise<void>;
|
|
10
|
+
clearTokens(): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Simple in-memory token storage implementation
|
|
14
|
+
*/
|
|
15
|
+
export declare class MemoryTokenStorage implements TokenStorage {
|
|
16
|
+
private accessToken;
|
|
17
|
+
private refreshToken;
|
|
18
|
+
getAccessToken(): Promise<string | null>;
|
|
19
|
+
setAccessToken(token: string): Promise<void>;
|
|
20
|
+
getRefreshToken(): Promise<string | null>;
|
|
21
|
+
setRefreshToken(token: string): Promise<void>;
|
|
22
|
+
clearTokens(): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Browser localStorage token storage implementation
|
|
26
|
+
*/
|
|
27
|
+
export declare class BrowserTokenStorage implements TokenStorage {
|
|
28
|
+
private accessTokenKey;
|
|
29
|
+
private refreshTokenKey;
|
|
30
|
+
constructor(prefix?: string);
|
|
31
|
+
getAccessToken(): Promise<string | null>;
|
|
32
|
+
setAccessToken(token: string): Promise<void>;
|
|
33
|
+
getRefreshToken(): Promise<string | null>;
|
|
34
|
+
setRefreshToken(token: string): Promise<void>;
|
|
35
|
+
clearTokens(): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Configuration for the auth middleware
|
|
39
|
+
*/
|
|
40
|
+
export interface AuthMiddlewareConfig {
|
|
41
|
+
/**
|
|
42
|
+
* Token storage implementation
|
|
43
|
+
*/
|
|
44
|
+
tokenStorage: TokenStorage;
|
|
45
|
+
/**
|
|
46
|
+
* API key for anonymous endpoints
|
|
47
|
+
*/
|
|
48
|
+
apiKey?: string;
|
|
49
|
+
/**
|
|
50
|
+
* Base URL for the API (used for refresh token endpoint)
|
|
51
|
+
*/
|
|
52
|
+
baseUrl: string;
|
|
53
|
+
/**
|
|
54
|
+
* Function to refresh tokens
|
|
55
|
+
* Should make a call to /auth/refresh-token endpoint
|
|
56
|
+
*/
|
|
57
|
+
refreshTokenFn?: (refreshToken: string) => Promise<{
|
|
58
|
+
access_token: string;
|
|
59
|
+
refresh_token: string;
|
|
60
|
+
}>;
|
|
61
|
+
/**
|
|
62
|
+
* Callback when tokens are updated (login/refresh)
|
|
63
|
+
*/
|
|
64
|
+
onTokensUpdated?: (accessToken: string, refreshToken: string) => void;
|
|
65
|
+
/**
|
|
66
|
+
* Callback when tokens are cleared (logout/error)
|
|
67
|
+
*/
|
|
68
|
+
onTokensCleared?: () => void;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Create authentication middleware for openapi-fetch
|
|
72
|
+
*/
|
|
73
|
+
export declare function createAuthMiddleware(config: AuthMiddlewareConfig): Middleware;
|
|
74
|
+
/**
|
|
75
|
+
* Helper function to create auth middleware with sensible defaults
|
|
76
|
+
*/
|
|
77
|
+
export declare function createDefaultAuthMiddleware(options: {
|
|
78
|
+
apiKey?: string;
|
|
79
|
+
baseUrl: string;
|
|
80
|
+
tokenStorage?: TokenStorage;
|
|
81
|
+
onTokensUpdated?: (accessToken: string, refreshToken: string) => void;
|
|
82
|
+
onTokensCleared?: () => void;
|
|
83
|
+
}): Middleware;
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { isTokenExpired } from "./jwt-utils";
|
|
2
|
+
import { getPathnameFromUrl, isAnonymousAuthEndpoint, isTokenReturningEndpoint, isLogoutEndpoint } from "./auth-utils";
|
|
3
|
+
/**
|
|
4
|
+
* Simple in-memory token storage implementation
|
|
5
|
+
*/
|
|
6
|
+
export class MemoryTokenStorage {
|
|
7
|
+
accessToken = null;
|
|
8
|
+
refreshToken = null;
|
|
9
|
+
async getAccessToken() {
|
|
10
|
+
return this.accessToken;
|
|
11
|
+
}
|
|
12
|
+
async setAccessToken(token) {
|
|
13
|
+
this.accessToken = token;
|
|
14
|
+
}
|
|
15
|
+
async getRefreshToken() {
|
|
16
|
+
return this.refreshToken;
|
|
17
|
+
}
|
|
18
|
+
async setRefreshToken(token) {
|
|
19
|
+
this.refreshToken = token;
|
|
20
|
+
}
|
|
21
|
+
async clearTokens() {
|
|
22
|
+
this.accessToken = null;
|
|
23
|
+
this.refreshToken = null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Browser localStorage token storage implementation
|
|
28
|
+
*/
|
|
29
|
+
export class BrowserTokenStorage {
|
|
30
|
+
accessTokenKey;
|
|
31
|
+
refreshTokenKey;
|
|
32
|
+
constructor(prefix = "storefront_") {
|
|
33
|
+
this.accessTokenKey = `${prefix}access_token`;
|
|
34
|
+
this.refreshTokenKey = `${prefix}refresh_token`;
|
|
35
|
+
}
|
|
36
|
+
async getAccessToken() {
|
|
37
|
+
if (typeof localStorage === "undefined")
|
|
38
|
+
return null;
|
|
39
|
+
return localStorage.getItem(this.accessTokenKey);
|
|
40
|
+
}
|
|
41
|
+
async setAccessToken(token) {
|
|
42
|
+
if (typeof localStorage !== "undefined") {
|
|
43
|
+
localStorage.setItem(this.accessTokenKey, token);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async getRefreshToken() {
|
|
47
|
+
if (typeof localStorage === "undefined")
|
|
48
|
+
return null;
|
|
49
|
+
return localStorage.getItem(this.refreshTokenKey);
|
|
50
|
+
}
|
|
51
|
+
async setRefreshToken(token) {
|
|
52
|
+
if (typeof localStorage !== "undefined") {
|
|
53
|
+
localStorage.setItem(this.refreshTokenKey, token);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async clearTokens() {
|
|
57
|
+
if (typeof localStorage !== "undefined") {
|
|
58
|
+
localStorage.removeItem(this.accessTokenKey);
|
|
59
|
+
localStorage.removeItem(this.refreshTokenKey);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Create authentication middleware for openapi-fetch
|
|
65
|
+
*/
|
|
66
|
+
export function createAuthMiddleware(config) {
|
|
67
|
+
let isRefreshing = false;
|
|
68
|
+
let refreshPromise = null;
|
|
69
|
+
const refreshTokens = async () => {
|
|
70
|
+
if (isRefreshing && refreshPromise) {
|
|
71
|
+
return refreshPromise;
|
|
72
|
+
}
|
|
73
|
+
isRefreshing = true;
|
|
74
|
+
refreshPromise = (async () => {
|
|
75
|
+
try {
|
|
76
|
+
const refreshToken = await config.tokenStorage.getRefreshToken();
|
|
77
|
+
let newTokens;
|
|
78
|
+
if (refreshToken && !isTokenExpired(refreshToken)) {
|
|
79
|
+
// Try normal refresh token flow first (only if refresh token is not expired)
|
|
80
|
+
if (config.refreshTokenFn) {
|
|
81
|
+
// Use provided refresh function
|
|
82
|
+
newTokens = await config.refreshTokenFn(refreshToken);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
// Default refresh implementation
|
|
86
|
+
const response = await fetch(`${config.baseUrl}/auth/refresh-token`, {
|
|
87
|
+
method: "POST",
|
|
88
|
+
headers: {
|
|
89
|
+
"Content-Type": "application/json",
|
|
90
|
+
},
|
|
91
|
+
body: JSON.stringify({ refresh_token: refreshToken }),
|
|
92
|
+
});
|
|
93
|
+
if (!response.ok) {
|
|
94
|
+
throw new Error(`Token refresh failed: ${response.status}`);
|
|
95
|
+
}
|
|
96
|
+
const data = await response.json();
|
|
97
|
+
newTokens = data.content;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
// No refresh token available OR refresh token is expired - try anonymous token fallback
|
|
102
|
+
// This handles cases where:
|
|
103
|
+
// 1. SDK was initialized with just an access token
|
|
104
|
+
// 2. Refresh token has expired
|
|
105
|
+
const currentAccessToken = await config.tokenStorage.getAccessToken();
|
|
106
|
+
if (!currentAccessToken) {
|
|
107
|
+
throw new Error("No tokens available for refresh");
|
|
108
|
+
}
|
|
109
|
+
const reason = refreshToken
|
|
110
|
+
? "refresh token expired"
|
|
111
|
+
: "no refresh token available";
|
|
112
|
+
// Get new anonymous tokens while preserving user_id
|
|
113
|
+
const response = await fetch(`${config.baseUrl}/auth/anonymous`, {
|
|
114
|
+
method: "POST",
|
|
115
|
+
headers: {
|
|
116
|
+
"Content-Type": "application/json",
|
|
117
|
+
...(config.apiKey && { "X-Api-Key": config.apiKey }),
|
|
118
|
+
"Authorization": `Bearer ${currentAccessToken}`, // For user_id continuity
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
if (!response.ok) {
|
|
122
|
+
throw new Error(`Anonymous token fallback failed: ${response.status}`);
|
|
123
|
+
}
|
|
124
|
+
const data = await response.json();
|
|
125
|
+
newTokens = data.content;
|
|
126
|
+
console.info(`Token refreshed via anonymous fallback (${reason}) - user may need to re-authenticate for privileged operations`);
|
|
127
|
+
}
|
|
128
|
+
// Store new tokens
|
|
129
|
+
await config.tokenStorage.setAccessToken(newTokens.access_token);
|
|
130
|
+
await config.tokenStorage.setRefreshToken(newTokens.refresh_token);
|
|
131
|
+
// Notify callback
|
|
132
|
+
config.onTokensUpdated?.(newTokens.access_token, newTokens.refresh_token);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
console.error("Token refresh failed:", error);
|
|
136
|
+
// Clear tokens on refresh failure
|
|
137
|
+
await config.tokenStorage.clearTokens();
|
|
138
|
+
config.onTokensCleared?.();
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
isRefreshing = false;
|
|
143
|
+
refreshPromise = null;
|
|
144
|
+
}
|
|
145
|
+
})();
|
|
146
|
+
return refreshPromise;
|
|
147
|
+
};
|
|
148
|
+
return {
|
|
149
|
+
async onRequest({ request }) {
|
|
150
|
+
const pathname = getPathnameFromUrl(request.url);
|
|
151
|
+
// Handle anonymous auth endpoint - use API key
|
|
152
|
+
if (isAnonymousAuthEndpoint(pathname)) {
|
|
153
|
+
if (config.apiKey) {
|
|
154
|
+
request.headers.set("X-Api-Key", config.apiKey);
|
|
155
|
+
}
|
|
156
|
+
// Also send existing access token if available (even if expired)
|
|
157
|
+
// This helps the server maintain anonymous user continuity
|
|
158
|
+
const existingToken = await config.tokenStorage.getAccessToken();
|
|
159
|
+
if (existingToken) {
|
|
160
|
+
request.headers.set("Authorization", `Bearer ${existingToken}`);
|
|
161
|
+
}
|
|
162
|
+
return request;
|
|
163
|
+
}
|
|
164
|
+
// For all other endpoints, use access token
|
|
165
|
+
let accessToken = await config.tokenStorage.getAccessToken();
|
|
166
|
+
// Check if token needs refresh
|
|
167
|
+
if (accessToken && isTokenExpired(accessToken)) {
|
|
168
|
+
try {
|
|
169
|
+
await refreshTokens();
|
|
170
|
+
accessToken = await config.tokenStorage.getAccessToken();
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
// If refresh fails, continue with expired token
|
|
174
|
+
// The server will return 401 and we'll handle it in onResponse
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Add Authorization header if we have a token
|
|
178
|
+
if (accessToken) {
|
|
179
|
+
request.headers.set("Authorization", `Bearer ${accessToken}`);
|
|
180
|
+
}
|
|
181
|
+
return request;
|
|
182
|
+
},
|
|
183
|
+
async onResponse({ request, response }) {
|
|
184
|
+
const pathname = getPathnameFromUrl(request.url);
|
|
185
|
+
// Handle successful responses that return tokens
|
|
186
|
+
if (response.ok) {
|
|
187
|
+
if (isTokenReturningEndpoint(pathname) || isAnonymousAuthEndpoint(pathname)) {
|
|
188
|
+
try {
|
|
189
|
+
const data = await response.clone().json();
|
|
190
|
+
const content = data.content;
|
|
191
|
+
if (content?.access_token && content?.refresh_token) {
|
|
192
|
+
await config.tokenStorage.setAccessToken(content.access_token);
|
|
193
|
+
await config.tokenStorage.setRefreshToken(content.refresh_token);
|
|
194
|
+
config.onTokensUpdated?.(content.access_token, content.refresh_token);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
console.warn("Failed to extract tokens from response:", error);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
else if (isLogoutEndpoint(pathname)) {
|
|
202
|
+
// Clear tokens on successful logout
|
|
203
|
+
await config.tokenStorage.clearTokens();
|
|
204
|
+
config.onTokensCleared?.();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// Handle 401 responses - only retry if token was expired
|
|
208
|
+
if (response.status === 401 && !isAnonymousAuthEndpoint(pathname)) {
|
|
209
|
+
const currentToken = await config.tokenStorage.getAccessToken();
|
|
210
|
+
// Only attempt refresh if we have a token and it's expired
|
|
211
|
+
// This prevents infinite retries for privilege-related 401s
|
|
212
|
+
if (currentToken && isTokenExpired(currentToken, 0)) {
|
|
213
|
+
try {
|
|
214
|
+
await refreshTokens();
|
|
215
|
+
// Retry the original request with new token
|
|
216
|
+
const newToken = await config.tokenStorage.getAccessToken();
|
|
217
|
+
if (newToken) {
|
|
218
|
+
const retryRequest = request.clone();
|
|
219
|
+
retryRequest.headers.set("Authorization", `Bearer ${newToken}`);
|
|
220
|
+
return fetch(retryRequest);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
// If refresh fails, let the original 401 response through
|
|
225
|
+
console.warn("Token refresh failed on 401 response:", error);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return response;
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Helper function to create auth middleware with sensible defaults
|
|
235
|
+
*/
|
|
236
|
+
export function createDefaultAuthMiddleware(options) {
|
|
237
|
+
const tokenStorage = options.tokenStorage ||
|
|
238
|
+
(typeof localStorage !== "undefined"
|
|
239
|
+
? new BrowserTokenStorage()
|
|
240
|
+
: new MemoryTokenStorage());
|
|
241
|
+
return createAuthMiddleware({
|
|
242
|
+
tokenStorage,
|
|
243
|
+
apiKey: options.apiKey,
|
|
244
|
+
baseUrl: options.baseUrl,
|
|
245
|
+
onTokensUpdated: options.onTokensUpdated,
|
|
246
|
+
onTokensCleared: options.onTokensCleared,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { ApiResult, CancelOrderBody, CancelOrderContent, CancelOrderPathParams, CreateOrderBody, CreateOrderContent, GetOrderDetailContent, GetOrderDetailPathParams, GetPaymentStatusContent, ListOrderPaymentsContent, ListOrderPaymentsPathParams, ListOrderRefundsContent, ListOrderRefundsPathParams, ListOrdersContent, ListOrderShipmentsContent, ListOrderShipmentsPathParams, ListOrdersQuery, RetryOrderPaymentBody, RetryOrderPaymentContent, RetryOrderPaymentPathParams } from "../types/storefront-api-types";
|
|
2
|
+
import { StorefrontAPIClient } from "./client";
|
|
3
|
+
/**
|
|
4
|
+
* Client for interacting with order endpoints
|
|
5
|
+
*/
|
|
6
|
+
export declare class OrderClient extends StorefrontAPIClient {
|
|
7
|
+
/**
|
|
8
|
+
* Get order details
|
|
9
|
+
*
|
|
10
|
+
* @param orderNumber - Order number
|
|
11
|
+
* @returns Promise with order details
|
|
12
|
+
*/
|
|
13
|
+
getOrderDetails(pathParams: GetOrderDetailPathParams): Promise<ApiResult<GetOrderDetailContent>>;
|
|
14
|
+
/**
|
|
15
|
+
* Create order
|
|
16
|
+
*
|
|
17
|
+
* @param cartId - Cart ID
|
|
18
|
+
* @param paymentGateway - Payment gateway
|
|
19
|
+
* @param paymentGatewayParams - Params for the selected payment gateway
|
|
20
|
+
* @returns Promise with order details
|
|
21
|
+
*/
|
|
22
|
+
createOrder(body: CreateOrderBody): Promise<ApiResult<CreateOrderContent>>;
|
|
23
|
+
/**
|
|
24
|
+
* List all orders
|
|
25
|
+
*
|
|
26
|
+
* @param queryParams - Query parameters
|
|
27
|
+
* @returns Promise with order details
|
|
28
|
+
*/
|
|
29
|
+
listOrders(queryParams: ListOrdersQuery): Promise<ApiResult<ListOrdersContent>>;
|
|
30
|
+
/**
|
|
31
|
+
* Get payment status for an order
|
|
32
|
+
*
|
|
33
|
+
* @param orderNumber - Order number
|
|
34
|
+
* @returns Promise with payment status
|
|
35
|
+
*/
|
|
36
|
+
getPaymentStatus(orderNumber: string): Promise<ApiResult<GetPaymentStatusContent>>;
|
|
37
|
+
/**
|
|
38
|
+
* Get all shipments for an order
|
|
39
|
+
*
|
|
40
|
+
* @param orderNumber - Order number
|
|
41
|
+
* @returns Promise with shipments
|
|
42
|
+
*/
|
|
43
|
+
listOrderShipments(pathParams: ListOrderShipmentsPathParams): Promise<ApiResult<ListOrderShipmentsContent>>;
|
|
44
|
+
/**
|
|
45
|
+
* List order payments
|
|
46
|
+
*
|
|
47
|
+
* @param orderNumber - Order number
|
|
48
|
+
* @returns Promise with payments
|
|
49
|
+
*/
|
|
50
|
+
listOrderPayments(pathParams: ListOrderPaymentsPathParams): Promise<ApiResult<ListOrderPaymentsContent>>;
|
|
51
|
+
/**
|
|
52
|
+
* List order refunds
|
|
53
|
+
*
|
|
54
|
+
* @param orderNumber - Order number
|
|
55
|
+
* @returns Promise with refunds
|
|
56
|
+
*/
|
|
57
|
+
listOrderRefunds(pathParams: ListOrderRefundsPathParams): Promise<ApiResult<ListOrderRefundsContent>>;
|
|
58
|
+
/**
|
|
59
|
+
* Cancel an order
|
|
60
|
+
*
|
|
61
|
+
* @param orderNumber - Order number
|
|
62
|
+
* @returns Promise with order details
|
|
63
|
+
*/
|
|
64
|
+
cancelOrder(pathParams: CancelOrderPathParams, body: CancelOrderBody): Promise<ApiResult<CancelOrderContent>>;
|
|
65
|
+
/**
|
|
66
|
+
* Retry payment for an order
|
|
67
|
+
*
|
|
68
|
+
* @param orderNumber - Order number
|
|
69
|
+
* @returns Promise with order details
|
|
70
|
+
*/
|
|
71
|
+
retryOrderPayment(pathParams: RetryOrderPaymentPathParams, body: RetryOrderPaymentBody): Promise<ApiResult<RetryOrderPaymentContent>>;
|
|
72
|
+
}
|