@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.
@@ -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
+ }