@kasuvia/sdk 0.0.2 → 0.0.5
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/hooks/index.d.mts +430 -0
- package/dist/hooks/index.d.ts +430 -0
- package/dist/hooks/index.js +3073 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/index.mjs +2962 -0
- package/dist/hooks/index.mjs.map +1 -0
- package/dist/index-CxNLI4nw.d.ts +786 -0
- package/dist/index-CzVMyUs4.d.mts +492 -0
- package/dist/index-CzVMyUs4.d.ts +492 -0
- package/dist/index-Dowl5224.d.mts +786 -0
- package/dist/index.d.mts +48 -1739
- package/dist/index.d.ts +48 -1739
- package/dist/index.js +1544 -2359
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1527 -2258
- package/dist/index.mjs.map +1 -1
- package/dist/sdk-context-BuarJbeb.d.ts +75 -0
- package/dist/sdk-context-Db8kf1Tv.d.mts +75 -0
- package/dist/services/index.d.mts +2 -0
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.js +2369 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/index.mjs +2252 -0
- package/dist/services/index.mjs.map +1 -0
- package/package.json +11 -2
- package/src/config/index.ts +10 -91
- package/src/constants.ts +13 -0
- package/src/context/sdk-context.tsx +70 -0
- package/src/graphql/collections.graphql.ts +2 -10
- package/src/graphql/products.graphql.ts +215 -65
- package/src/graphql/wishlist.graphql.ts +7 -6
- package/src/hooks/index.ts +2 -0
- package/src/hooks/use-auth.ts +82 -158
- package/src/hooks/use-bespoke-availability.ts +25 -8
- package/src/hooks/use-cart.ts +222 -119
- package/src/hooks/use-cms.ts +179 -86
- package/src/hooks/use-collections.ts +52 -15
- package/src/hooks/use-consultation-stream.ts +4 -3
- package/src/hooks/use-consultations.ts +120 -51
- package/src/hooks/use-countries.ts +35 -13
- package/src/hooks/use-currencies.ts +26 -13
- package/src/hooks/use-google-translate.ts +22 -8
- package/src/hooks/use-languages.ts +40 -20
- package/src/hooks/use-mobile.ts +23 -0
- package/src/hooks/use-order-stream.ts +9 -5
- package/src/hooks/use-orders.ts +99 -44
- package/src/hooks/use-payment.ts +108 -40
- package/src/hooks/use-products.ts +251 -53
- package/src/hooks/use-service-booking.ts +152 -90
- package/src/hooks/use-states.ts +35 -15
- package/src/hooks/use-wishlist.ts +92 -91
- package/src/index.ts +26 -30
- package/src/services/auth.service.ts +209 -167
- package/src/services/bespoke.service.ts +12 -4
- package/src/services/cart.service.ts +172 -70
- package/src/services/cms.service.ts +70 -35
- package/src/services/collection.service.ts +22 -5
- package/src/services/config.service.ts +34 -14
- package/src/services/consultation.service.ts +72 -18
- package/src/services/logistics.service.ts +35 -11
- package/src/services/notifications.service.ts +27 -12
- package/src/services/order.service.ts +74 -15
- package/src/services/payment.service.ts +86 -31
- package/src/services/product.service.ts +279 -39
- package/src/services/service-booking.service.ts +98 -26
- package/src/services/wishlist.service.ts +69 -25
- package/src/types/auth/index.ts +6 -4
- package/src/types/business/index.ts +7 -2
- package/src/types/payments/index.ts +3 -3
- package/src/types/services/index.ts +2 -5
- package/src/types/store/index.ts +38 -3
- package/src/utils/error-handler.ts +10 -5
- package/src/utils/storage.ts +48 -26
- package/src/utils/validators.ts +85 -0
- package/src/client/authClient.ts +0 -135
- package/src/client/graphqlClient.ts +0 -53
- package/src/client/httpClient.ts +0 -298
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Customer auth service — all API calls scoped to this storefront's business tenant.
|
|
3
|
-
*
|
|
4
|
-
* - Authorization: Bearer <accessToken> (from interceptors)
|
|
5
|
-
* - X-Storefront-Key: <storefrontApiKey> (from interceptors)
|
|
6
|
-
*
|
|
7
|
-
* The `tenant_id` field in login/register payloads is the permanent business_id (B-XXXXXXXX).
|
|
3
|
+
* Stateless - all configuration passed as parameters.
|
|
8
4
|
*/
|
|
9
|
-
import {
|
|
10
|
-
import { tokenStore } from '../client/authClient'
|
|
11
|
-
import { resetGraphQLClient } from '../client/graphqlClient'
|
|
12
|
-
import { getRuntimeConfig } from '../config'
|
|
5
|
+
import type { SdkConfig } from '../config'
|
|
13
6
|
import { logger } from '../utils/logger'
|
|
14
|
-
import {
|
|
7
|
+
import { STORAGE_KEYS } from '../constants'
|
|
15
8
|
import type {
|
|
16
9
|
CustomerAuthResponse,
|
|
17
10
|
CustomerLoginPayload,
|
|
@@ -27,94 +20,127 @@ import type {
|
|
|
27
20
|
|
|
28
21
|
/**
|
|
29
22
|
* POST /auth/login/customer/
|
|
30
|
-
* The `tenant_id` is automatically filled from config.
|
|
23
|
+
* The `tenant_id` is automatically filled from config.businessId.
|
|
31
24
|
*/
|
|
32
|
-
export async function loginCustomer(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
25
|
+
export async function loginCustomer(
|
|
26
|
+
config: SdkConfig,
|
|
27
|
+
payload: Omit<CustomerLoginPayload, 'tenant_id'>
|
|
28
|
+
): Promise<CustomerAuthResponse> {
|
|
29
|
+
const response = await fetch(`${config.backendUrls.auth}/auth/login/customer/`, {
|
|
30
|
+
method: 'POST',
|
|
31
|
+
headers: {
|
|
32
|
+
'Content-Type': 'application/json',
|
|
33
|
+
'X-Storefront-Key': config.storefrontApiKey,
|
|
34
|
+
'X-Business-ID': config.businessId,
|
|
35
|
+
},
|
|
36
|
+
body: JSON.stringify({
|
|
37
|
+
...payload,
|
|
38
|
+
tenant_id: config.businessId,
|
|
39
|
+
}),
|
|
37
40
|
})
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
// Merge guest cart after successful login
|
|
44
|
-
await mergeGuestCartIfPresent()
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
throw new Error('Login failed')
|
|
44
|
+
}
|
|
45
45
|
|
|
46
|
+
const data = await response.json()
|
|
46
47
|
return data
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
/**
|
|
50
51
|
* POST /auth/register/customer/
|
|
51
|
-
* The `tenant_id` is automatically filled from config.
|
|
52
|
+
* The `tenant_id` is automatically filled from config.businessId.
|
|
52
53
|
*/
|
|
53
|
-
export async function registerCustomer(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
export async function registerCustomer(
|
|
55
|
+
config: SdkConfig,
|
|
56
|
+
payload: Omit<CustomerRegisterPayload, 'tenant_id'>
|
|
57
|
+
): Promise<CustomerAuthResponse> {
|
|
58
|
+
const response = await fetch(`${config.backendUrls.auth}/auth/register/customer/`, {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
headers: {
|
|
61
|
+
'Content-Type': 'application/json',
|
|
62
|
+
'X-Storefront-Key': config.storefrontApiKey,
|
|
63
|
+
'X-Business-ID': config.businessId,
|
|
64
|
+
},
|
|
65
|
+
body: JSON.stringify({
|
|
66
|
+
...payload,
|
|
67
|
+
tenant_id: config.businessId,
|
|
68
|
+
}),
|
|
58
69
|
})
|
|
59
70
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
// Merge guest cart after successful registration
|
|
65
|
-
await mergeGuestCartIfPresent()
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
throw new Error('Registration failed')
|
|
73
|
+
}
|
|
66
74
|
|
|
75
|
+
const data = await response.json()
|
|
67
76
|
return data
|
|
68
77
|
}
|
|
69
78
|
|
|
70
79
|
/** POST /auth/token/refresh/
|
|
71
|
-
* CRITICAL: Uses refreshAxios (no interceptors) to prevent interceptor recursion.
|
|
72
80
|
*/
|
|
73
|
-
export async function refreshToken(
|
|
74
|
-
|
|
81
|
+
export async function refreshToken(
|
|
82
|
+
config: SdkConfig,
|
|
83
|
+
refresh: string
|
|
84
|
+
): Promise<RefreshTokenResponse> {
|
|
85
|
+
const response = await fetch(`${config.backendUrls.auth}/auth/token/refresh/`, {
|
|
86
|
+
method: 'POST',
|
|
87
|
+
headers: {
|
|
88
|
+
'Content-Type': 'application/json',
|
|
89
|
+
},
|
|
90
|
+
body: JSON.stringify({ refresh }),
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
if (!response.ok) {
|
|
94
|
+
throw new Error('Token refresh failed')
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const data = await response.json()
|
|
75
98
|
return data
|
|
76
99
|
}
|
|
77
100
|
|
|
78
|
-
/** POST /auth/logout/ — revokes the server-side session.
|
|
79
|
-
export async function logout(
|
|
80
|
-
|
|
101
|
+
/** POST /auth/logout/ — revokes the server-side session. */
|
|
102
|
+
export async function logout(
|
|
103
|
+
config: SdkConfig,
|
|
104
|
+
refresh?: string
|
|
105
|
+
): Promise<void> {
|
|
81
106
|
const payload: LogoutPayload = refresh ? { refresh } : {}
|
|
82
107
|
try {
|
|
83
|
-
await
|
|
108
|
+
await fetch(`${config.backendUrls.auth}/auth/logout/`, {
|
|
109
|
+
method: 'POST',
|
|
110
|
+
headers: {
|
|
111
|
+
'Content-Type': 'application/json',
|
|
112
|
+
},
|
|
113
|
+
body: JSON.stringify(payload),
|
|
114
|
+
})
|
|
84
115
|
} catch {
|
|
85
|
-
// Swallow server errors
|
|
86
|
-
// Local token cleanup in `finally` is what actually logs the user out.
|
|
87
|
-
} finally {
|
|
88
|
-
tokenStore.clearAll()
|
|
89
|
-
resetGraphQLClient()
|
|
116
|
+
// Swallow server errors
|
|
90
117
|
}
|
|
91
118
|
}
|
|
92
119
|
|
|
93
120
|
/**
|
|
94
121
|
* Verify a social provider id_token (or access_token for Facebook) with the backend.
|
|
95
|
-
* The backend validates the token with the provider and creates/finds the customer.
|
|
96
|
-
*
|
|
97
|
-
* Industry-standard "ID Token Exchange" pattern:
|
|
98
|
-
* 1. Customer completes Google/Facebook sign-in on the storefront
|
|
99
|
-
* 2. Storefront receives an id_token / access_token from the provider
|
|
100
|
-
* 3. This function sends it to the backend for server-side verification
|
|
101
|
-
* 4. Backend returns our JWT tokens
|
|
102
122
|
*/
|
|
103
123
|
export async function verifySocialToken(
|
|
124
|
+
config: SdkConfig,
|
|
104
125
|
provider: 'google' | 'facebook',
|
|
105
126
|
token: string
|
|
106
127
|
): Promise<CustomerAuthResponse> {
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
128
|
+
const response = await fetch(`${config.backendUrls.auth}/auth/social/verify/${provider}/`, {
|
|
129
|
+
method: 'POST',
|
|
130
|
+
headers: {
|
|
131
|
+
'Content-Type': 'application/json',
|
|
132
|
+
},
|
|
133
|
+
body: JSON.stringify({
|
|
111
134
|
id_token: token,
|
|
112
135
|
storefront_key: config.storefrontApiKey,
|
|
113
|
-
}
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
136
|
+
}),
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
if (!response.ok) {
|
|
140
|
+
throw new Error('Social token verification failed')
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const data = await response.json()
|
|
118
144
|
return data
|
|
119
145
|
}
|
|
120
146
|
|
|
@@ -143,7 +169,7 @@ function resolveAvatar(profilePicture: unknown): string | null {
|
|
|
143
169
|
return null
|
|
144
170
|
}
|
|
145
171
|
|
|
146
|
-
/** Map
|
|
172
|
+
/** Map kasuvia-auth UserSerializer response to storefront CustomerUser. */
|
|
147
173
|
export function mapAuthUserToCustomer(data: AuthUserResponse, provider?: CustomerUser['provider']): CustomerUser {
|
|
148
174
|
const firstname = data.profile?.firstname?.trim() || undefined
|
|
149
175
|
const lastname = data.profile?.lastname?.trim() || undefined
|
|
@@ -184,130 +210,112 @@ function buildCustomerFromClaims(claims: Record<string, unknown>): CustomerUser
|
|
|
184
210
|
}
|
|
185
211
|
|
|
186
212
|
/** GET /auth/profile/ */
|
|
187
|
-
export async function getMe(): Promise<CustomerUser> {
|
|
188
|
-
|
|
213
|
+
export async function getMe(config?: SdkConfig, accessToken?: string): Promise<CustomerUser> {
|
|
214
|
+
if (!config || !accessToken) {
|
|
215
|
+
const response = await fetch('/api/auth/me', { credentials: 'include' })
|
|
216
|
+
if (!response.ok) throw new Error('Failed to fetch profile')
|
|
217
|
+
const data = await response.json()
|
|
218
|
+
return data.user
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const response = await fetch(`${config.backendUrls.auth}/auth/profile/`, {
|
|
222
|
+
headers: {
|
|
223
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
224
|
+
},
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
if (!response.ok) {
|
|
228
|
+
throw new Error('Failed to fetch profile')
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const data = await response.json()
|
|
189
232
|
return mapAuthUserToCustomer(data)
|
|
190
233
|
}
|
|
191
234
|
|
|
192
235
|
/** GET /auth/profile/ - alias for getMe for backward compatibility */
|
|
193
|
-
export async function getAuthUser(): Promise<AuthUserResponse> {
|
|
194
|
-
|
|
236
|
+
export async function getAuthUser(config?: SdkConfig, accessToken?: string): Promise<AuthUserResponse> {
|
|
237
|
+
if (!config || !accessToken) {
|
|
238
|
+
const response = await fetch('/api/auth/me', { credentials: 'include' })
|
|
239
|
+
if (!response.ok) throw new Error('Failed to fetch auth user')
|
|
240
|
+
const data = await response.json()
|
|
241
|
+
return data.user
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const response = await fetch(`${config.backendUrls.auth}/auth/profile/`, {
|
|
245
|
+
headers: {
|
|
246
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
247
|
+
},
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
if (!response.ok) {
|
|
251
|
+
throw new Error('Failed to fetch auth user')
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const data = await response.json()
|
|
195
255
|
return data
|
|
196
256
|
}
|
|
197
257
|
|
|
198
258
|
/** PUT /auth/profile/ — update firstname, lastname, phone */
|
|
199
|
-
export async function updateProfile(
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
259
|
+
export async function updateProfile(
|
|
260
|
+
configOrPayload: SdkConfig | UpdateProfilePayload,
|
|
261
|
+
accessTokenOrPayload?: string | UpdateProfilePayload,
|
|
262
|
+
payloadParam?: UpdateProfilePayload
|
|
263
|
+
): Promise<CustomerUser> {
|
|
264
|
+
const isArg1Config = typeof configOrPayload === 'object' && configOrPayload !== null && 'businessId' in configOrPayload
|
|
265
|
+
if (!isArg1Config) {
|
|
266
|
+
const payload = configOrPayload as UpdateProfilePayload
|
|
267
|
+
const response = await fetch('/api/auth/profile', {
|
|
268
|
+
method: 'PUT',
|
|
269
|
+
headers: { 'Content-Type': 'application/json' },
|
|
270
|
+
credentials: 'include',
|
|
271
|
+
body: JSON.stringify(payload),
|
|
272
|
+
})
|
|
273
|
+
if (!response.ok) throw new Error('Failed to update profile')
|
|
274
|
+
return response.json()
|
|
275
|
+
}
|
|
205
276
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
277
|
+
const config = configOrPayload as SdkConfig
|
|
278
|
+
const accessToken = accessTokenOrPayload as string
|
|
279
|
+
const payload = payloadParam as UpdateProfilePayload
|
|
280
|
+
|
|
281
|
+
const response = await fetch(`${config.backendUrls.auth}/auth/profile/`, {
|
|
282
|
+
method: 'PUT',
|
|
283
|
+
headers: {
|
|
284
|
+
'Content-Type': 'application/json',
|
|
285
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
286
|
+
},
|
|
287
|
+
body: JSON.stringify(payload),
|
|
288
|
+
})
|
|
216
289
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
*/
|
|
220
|
-
async function retryWithBackoff<T>(
|
|
221
|
-
fn: () => Promise<T>,
|
|
222
|
-
maxRetries: number = 2,
|
|
223
|
-
baseDelay: number = 1000
|
|
224
|
-
): Promise<T> {
|
|
225
|
-
let lastError: unknown
|
|
226
|
-
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
227
|
-
try {
|
|
228
|
-
return await fn()
|
|
229
|
-
} catch (error) {
|
|
230
|
-
lastError = error
|
|
231
|
-
const { isNetworkError, isAuthError } = classifyError(error)
|
|
232
|
-
|
|
233
|
-
// Only retry on network errors, not auth errors
|
|
234
|
-
if (!isNetworkError || isAuthError) {
|
|
235
|
-
throw error
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Don't retry on the last attempt
|
|
239
|
-
if (attempt === maxRetries) {
|
|
240
|
-
throw error
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// Exponential backoff: 1s, 2s, 4s, etc.
|
|
244
|
-
const delay = baseDelay * Math.pow(2, attempt)
|
|
245
|
-
await new Promise(resolve => setTimeout(resolve, delay))
|
|
246
|
-
}
|
|
290
|
+
if (!response.ok) {
|
|
291
|
+
throw new Error('Failed to update profile')
|
|
247
292
|
}
|
|
248
|
-
|
|
293
|
+
|
|
294
|
+
const data = await response.json()
|
|
295
|
+
return mapAuthUserToCustomer(data)
|
|
249
296
|
}
|
|
250
297
|
|
|
251
298
|
/**
|
|
252
299
|
* Silently refresh the access token on app boot.
|
|
253
300
|
* Builds CustomerUser from JWT claims — no additional profile API call needed.
|
|
254
301
|
* Returns null if no refresh token exists or if the refresh fails.
|
|
255
|
-
* Includes a concurrent lock to prevent React Strict Mode from double-firing and blacklisting the token.
|
|
256
|
-
*
|
|
257
|
-
* Network error resilience:
|
|
258
|
-
* - Classifies errors as network vs auth
|
|
259
|
-
* - Retries transient network errors with exponential backoff
|
|
260
|
-
* - Immediately fails on auth errors (401/403) to avoid infinite loops
|
|
261
|
-
* - Clears auth state on any failure to ensure consistent logout behavior
|
|
262
302
|
*/
|
|
263
|
-
export async function silentRefresh(
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
try {
|
|
271
|
-
// Use retry with backoff for network resilience
|
|
272
|
-
const tokens = await retryWithBackoff(() => refreshToken(refresh), 2, 1000)
|
|
273
|
-
|
|
274
|
-
tokenStore.setAccessToken(tokens.access)
|
|
275
|
-
if (tokens.refresh) tokenStore.setRefreshToken(tokens.refresh, tokenStore.isPersistent())
|
|
276
|
-
|
|
277
|
-
// Decode user identity from the new access token — avoids a fragile /profile/ round-trip
|
|
278
|
-
const claims = parseJwtPayload(tokens.access)
|
|
279
|
-
const user = buildCustomerFromClaims(claims)
|
|
280
|
-
|
|
281
|
-
return { user, provider: user.provider }
|
|
282
|
-
} catch (error: unknown) {
|
|
283
|
-
const { isNetworkError, isAuthError, isBlacklistedError } = classifyError(error)
|
|
284
|
-
|
|
285
|
-
if (isNetworkError) {
|
|
286
|
-
logger.warn('[silentRefresh] Network error during token refresh (offline/CORS), clearing session:', error)
|
|
287
|
-
} else if (isBlacklistedError) {
|
|
288
|
-
logger.log('[silentRefresh] Token was blacklisted (likely from logout), clearing session')
|
|
289
|
-
} else if (isAuthError) {
|
|
290
|
-
logger.log('[silentRefresh] Token refresh failed with auth error (expired/invalid), clearing session')
|
|
291
|
-
} else {
|
|
292
|
-
logger.error('[silentRefresh] Unexpected error during token refresh, clearing session:', error)
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// CRITICAL: Clear auth state immediately on ANY refresh failure
|
|
296
|
-
// This prevents partial session state and ensures consistent logout behavior
|
|
297
|
-
tokenStore.clearAll()
|
|
303
|
+
export async function silentRefresh(
|
|
304
|
+
config: SdkConfig,
|
|
305
|
+
refresh: string
|
|
306
|
+
): Promise<{ user: CustomerUser; provider: CustomerUser['provider']; newAccessToken: string; newRefreshToken: string } | null> {
|
|
307
|
+
try {
|
|
308
|
+
const data = await refreshToken(config, refresh)
|
|
309
|
+
if (!data.access || !data.refresh) {
|
|
298
310
|
return null
|
|
299
|
-
} finally {
|
|
300
|
-
refreshPromise = null;
|
|
301
311
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
tokenStore.setAccessToken(access)
|
|
310
|
-
tokenStore.setRefreshToken(refresh, remember)
|
|
312
|
+
const claims = parseJwtPayload(data.access)
|
|
313
|
+
const user = buildCustomerFromClaims(claims)
|
|
314
|
+
const provider = user.provider
|
|
315
|
+
return { user, provider, newAccessToken: data.access, newRefreshToken: data.refresh }
|
|
316
|
+
} catch {
|
|
317
|
+
return null
|
|
318
|
+
}
|
|
311
319
|
}
|
|
312
320
|
|
|
313
321
|
// ── Helper Functions ─────────────────────────────────────────────────────────
|
|
@@ -330,3 +338,37 @@ function splitFullName(fullName?: string): { firstname: string; lastname: string
|
|
|
330
338
|
lastname: parts.slice(1).join(' ')
|
|
331
339
|
}
|
|
332
340
|
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Store tokens in localStorage (client-side utility)
|
|
344
|
+
* This is a convenience function for storefronts to store auth tokens
|
|
345
|
+
*/
|
|
346
|
+
export function storeTokens(accessToken: string, refreshToken: string): void {
|
|
347
|
+
if (typeof window !== 'undefined') {
|
|
348
|
+
localStorage.setItem(STORAGE_KEYS.ACCESS_TOKEN, accessToken)
|
|
349
|
+
localStorage.setItem(STORAGE_KEYS.REFRESH_TOKEN, refreshToken)
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Clear tokens from localStorage (client-side utility)
|
|
355
|
+
*/
|
|
356
|
+
export function clearTokens(): void {
|
|
357
|
+
if (typeof window !== 'undefined') {
|
|
358
|
+
localStorage.removeItem(STORAGE_KEYS.ACCESS_TOKEN)
|
|
359
|
+
localStorage.removeItem(STORAGE_KEYS.REFRESH_TOKEN)
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Get stored tokens from localStorage (client-side utility)
|
|
365
|
+
*/
|
|
366
|
+
export function getStoredTokens(): { accessToken: string | null; refreshToken: string | null } {
|
|
367
|
+
if (typeof window === 'undefined') {
|
|
368
|
+
return { accessToken: null, refreshToken: null }
|
|
369
|
+
}
|
|
370
|
+
return {
|
|
371
|
+
accessToken: localStorage.getItem(STORAGE_KEYS.ACCESS_TOKEN),
|
|
372
|
+
refreshToken: localStorage.getItem(STORAGE_KEYS.REFRESH_TOKEN),
|
|
373
|
+
}
|
|
374
|
+
}
|
|
@@ -2,13 +2,21 @@
|
|
|
2
2
|
* Bespoke Service
|
|
3
3
|
*
|
|
4
4
|
* Handles bespoke product availability checks.
|
|
5
|
+
* Stateless - all configuration passed as parameters.
|
|
5
6
|
*/
|
|
6
|
-
import {
|
|
7
|
+
import type { SdkConfig } from '../config'
|
|
7
8
|
import type { BespokeAvailability } from '../types/consultations'
|
|
8
9
|
|
|
9
|
-
export async function checkAvailability(product_id: string): Promise<BespokeAvailability> {
|
|
10
|
-
const
|
|
11
|
-
|
|
10
|
+
export async function checkAvailability(config: SdkConfig, product_id: string): Promise<BespokeAvailability> {
|
|
11
|
+
const response = await fetch(`${config.backendUrls.store}/bespoke/availability/${product_id}`, {
|
|
12
|
+
headers: {
|
|
13
|
+
"X-Storefront-Key": config.storefrontApiKey,
|
|
14
|
+
"X-Business-ID": config.businessId,
|
|
15
|
+
},
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
if (!response.ok) throw new Error('Failed to check bespoke availability')
|
|
19
|
+
return response.json()
|
|
12
20
|
}
|
|
13
21
|
|
|
14
22
|
// Service object export for backward compatibility
|