@kasuvia/sdk 0.0.2 → 0.0.4

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.
Files changed (67) hide show
  1. package/dist/hooks/index.d.mts +429 -0
  2. package/dist/hooks/index.d.ts +429 -0
  3. package/dist/hooks/index.js +3073 -0
  4. package/dist/hooks/index.js.map +1 -0
  5. package/dist/hooks/index.mjs +2962 -0
  6. package/dist/hooks/index.mjs.map +1 -0
  7. package/dist/index.d.mts +250 -1161
  8. package/dist/index.d.ts +250 -1161
  9. package/dist/index.js +1544 -2359
  10. package/dist/index.js.map +1 -1
  11. package/dist/index.mjs +1527 -2258
  12. package/dist/index.mjs.map +1 -1
  13. package/dist/sdk-context-Cy51kEKT.d.mts +565 -0
  14. package/dist/sdk-context-Cy51kEKT.d.ts +565 -0
  15. package/package.json +6 -2
  16. package/src/config/index.ts +10 -91
  17. package/src/constants.ts +13 -0
  18. package/src/context/sdk-context.tsx +70 -0
  19. package/src/graphql/collections.graphql.ts +2 -10
  20. package/src/graphql/products.graphql.ts +215 -65
  21. package/src/graphql/wishlist.graphql.ts +7 -6
  22. package/src/hooks/index.ts +2 -0
  23. package/src/hooks/use-auth.ts +82 -158
  24. package/src/hooks/use-bespoke-availability.ts +25 -8
  25. package/src/hooks/use-cart.ts +222 -119
  26. package/src/hooks/use-cms.ts +179 -86
  27. package/src/hooks/use-collections.ts +52 -15
  28. package/src/hooks/use-consultation-stream.ts +4 -3
  29. package/src/hooks/use-consultations.ts +120 -51
  30. package/src/hooks/use-countries.ts +35 -13
  31. package/src/hooks/use-currencies.ts +26 -13
  32. package/src/hooks/use-google-translate.ts +22 -8
  33. package/src/hooks/use-languages.ts +40 -20
  34. package/src/hooks/use-mobile.ts +23 -0
  35. package/src/hooks/use-order-stream.ts +9 -5
  36. package/src/hooks/use-orders.ts +99 -44
  37. package/src/hooks/use-payment.ts +108 -40
  38. package/src/hooks/use-products.ts +251 -53
  39. package/src/hooks/use-service-booking.ts +152 -90
  40. package/src/hooks/use-states.ts +35 -15
  41. package/src/hooks/use-wishlist.ts +92 -91
  42. package/src/index.ts +26 -30
  43. package/src/services/auth.service.ts +209 -167
  44. package/src/services/bespoke.service.ts +12 -4
  45. package/src/services/cart.service.ts +172 -70
  46. package/src/services/cms.service.ts +70 -35
  47. package/src/services/collection.service.ts +22 -5
  48. package/src/services/config.service.ts +34 -14
  49. package/src/services/consultation.service.ts +72 -18
  50. package/src/services/logistics.service.ts +35 -11
  51. package/src/services/notifications.service.ts +27 -12
  52. package/src/services/order.service.ts +74 -15
  53. package/src/services/payment.service.ts +86 -31
  54. package/src/services/product.service.ts +279 -39
  55. package/src/services/service-booking.service.ts +98 -26
  56. package/src/services/wishlist.service.ts +69 -25
  57. package/src/types/auth/index.ts +6 -4
  58. package/src/types/business/index.ts +7 -2
  59. package/src/types/payments/index.ts +3 -3
  60. package/src/types/services/index.ts +2 -5
  61. package/src/types/store/index.ts +38 -3
  62. package/src/utils/error-handler.ts +10 -5
  63. package/src/utils/storage.ts +48 -26
  64. package/src/utils/validators.ts +85 -0
  65. package/src/client/authClient.ts +0 -135
  66. package/src/client/graphqlClient.ts +0 -53
  67. package/src/client/httpClient.ts +0 -298
@@ -1,135 +0,0 @@
1
- /**
2
- * Auth Client - Token Store and Refresh Logic
3
- *
4
- * - Access token: in-memory only (XSS protection)
5
- * - Refresh token: localStorage (persists across page loads), keyed per tenant
6
- */
7
- import axios from 'axios'
8
- import { getRuntimeConfig, getBackendUrl, isInitialized } from '../config'
9
-
10
- let _accessToken: string | null = null
11
- let _isRefreshing = false
12
- let _pendingQueue: Array<{
13
- resolve: (newAccessToken: string) => void
14
- reject: (error: unknown) => void
15
- }> = []
16
-
17
- /**
18
- * Returns a BID-prefixed key for storage isolation.
19
- * Prevents data leakage when multiple storefronts run on the same domain.
20
- */
21
- function getStorageKey(): string {
22
- if (!isInitialized()) return 'kasuvia-sf-anon-refresh'
23
- const config = getRuntimeConfig()
24
- return `kasuvia-sf-${config.businessId}-refresh`
25
- }
26
-
27
- function drainQueue(error: unknown, newToken: string | null = null): void {
28
- _pendingQueue.forEach(({ resolve, reject }) => {
29
- if (newToken) resolve(newToken)
30
- else reject(error)
31
- })
32
- _pendingQueue = []
33
- }
34
-
35
- export const tokenStore = {
36
- getAccessToken: (): string | null => _accessToken,
37
-
38
- setAccessToken: (token: string | null): void => {
39
- _accessToken = token
40
- },
41
-
42
- getRefreshToken: (): string | null => {
43
- if (typeof window === 'undefined') return null
44
- const key = getStorageKey()
45
- return localStorage.getItem(key) || sessionStorage.getItem(key)
46
- },
47
-
48
- setRefreshToken: (token: string | null, remember = true): void => {
49
- if (typeof window === 'undefined') return
50
- const key = getStorageKey()
51
- localStorage.removeItem(key)
52
- sessionStorage.removeItem(key)
53
- if (token) {
54
- const store = remember ? localStorage : sessionStorage
55
- store.setItem(key, token)
56
- }
57
- },
58
-
59
- clearAll: (): void => {
60
- _accessToken = null
61
- if (typeof window !== 'undefined') {
62
- const key = getStorageKey()
63
- localStorage.removeItem(key)
64
- sessionStorage.removeItem(key)
65
- }
66
- },
67
-
68
- hasSession: (): boolean => {
69
- if (typeof window === 'undefined') return false
70
- const key = getStorageKey()
71
- return !!(localStorage.getItem(key) || sessionStorage.getItem(key))
72
- },
73
-
74
- isPersistent: (): boolean => {
75
- if (typeof window === 'undefined') return true
76
- const key = getStorageKey()
77
- return !!localStorage.getItem(key)
78
- },
79
- } as const
80
-
81
- /**
82
- * Refresh-only Axios instance.
83
- * CRITICAL: NO auth interceptors — avoids infinite recursion when the 401
84
- * interceptor on authAxios calls refreshToken() and itself receives a 401.
85
- * baseURL is set eagerly from the static BACKEND_URLS constant.
86
- */
87
- const refreshAxios = axios.create({
88
- baseURL: getBackendUrl('auth'),
89
- headers: {
90
- 'Content-Type': 'application/json',
91
- Accept: 'application/json',
92
- },
93
- withCredentials: true,
94
- timeout: 30_000,
95
- })
96
-
97
- /**
98
- * Perform token refresh with queue-coalescing to avoid parallel refresh races.
99
- */
100
- export async function refreshToken(): Promise<string> {
101
- if (_isRefreshing) {
102
- // Queue subsequent callers so they all get the new token from a single refresh
103
- return new Promise((resolve, reject) => {
104
- _pendingQueue.push({ resolve, reject })
105
- })
106
- }
107
-
108
- _isRefreshing = true
109
-
110
- const refresh = tokenStore.getRefreshToken()
111
- if (!refresh) {
112
- _isRefreshing = false
113
- tokenStore.clearAll()
114
- throw new Error('[kasuvia SDK] No refresh token available')
115
- }
116
-
117
- try {
118
- const { data } = await refreshAxios.post('/auth/token/refresh/', { refresh })
119
- const newAccess: string = data.access
120
- tokenStore.setAccessToken(newAccess)
121
-
122
- if (data.refresh) {
123
- tokenStore.setRefreshToken(data.refresh, tokenStore.isPersistent())
124
- }
125
-
126
- drainQueue(null, newAccess)
127
- return newAccess
128
- } catch (err) {
129
- drainQueue(err)
130
- tokenStore.clearAll()
131
- throw err
132
- } finally {
133
- _isRefreshing = false
134
- }
135
- }
@@ -1,53 +0,0 @@
1
- /**
2
- * GraphQL Client
3
- *
4
- * Provides a GraphQL client with tenant and auth headers for product queries.
5
- * The endpoint URL is derived from the static BACKEND_URLS constant (eager,
6
- * module-load safe). Auth headers are injected fresh on each request via a
7
- * factory so the token is always current.
8
- */
9
- import { GraphQLClient } from 'graphql-request'
10
- import { getRuntimeConfig, getBackendUrl, isInitialized } from '../config'
11
- import { tokenStore } from './authClient'
12
-
13
- const GRAPHQL_URL = `${getBackendUrl('store')}/graphql`
14
-
15
- /**
16
- * Build fresh request headers.
17
- * Called on every getGraphQLClient() so token changes are always reflected.
18
- */
19
- function buildHeaders(): Record<string, string> {
20
- const headers: Record<string, string> = {
21
- 'Content-Type': 'application/json',
22
- Accept: 'application/json',
23
- }
24
-
25
- if (isInitialized()) {
26
- const config = getRuntimeConfig()
27
- headers['X-Storefront-Key'] = config.storefrontApiKey
28
- headers['X-Business-ID'] = config.businessId
29
- }
30
-
31
- const token = tokenStore.getAccessToken()
32
- if (token) headers['Authorization'] = `Bearer ${token}`
33
-
34
- return headers
35
- }
36
-
37
- /**
38
- * Returns a GraphQL client instance with up-to-date auth headers.
39
- * A new client is created on each call so token/config changes take effect
40
- * immediately without requiring an explicit reset.
41
- */
42
- export function getGraphQLClient(): GraphQLClient {
43
- return new GraphQLClient(GRAPHQL_URL, { headers: buildHeaders() })
44
- }
45
-
46
- /**
47
- * No-op — kept for backward compatibility.
48
- * Clients are now stateless (created fresh on each call), so there is no
49
- * cached instance to reset.
50
- */
51
- export function resetGraphQLClient(): void {
52
- // No-op: clients are built fresh on every call.
53
- }
@@ -1,298 +0,0 @@
1
- /**
2
- * HTTP Client - Pre-configured Axios instances
3
- *
4
- * Each Axios instance is created with a default baseURL based on the static
5
- * service URL. The baseURLs are complete URLs including the API version,
6
- * selected based on the configured environment (development/production).
7
- */
8
- import axios, { type AxiosInstance } from 'axios'
9
- import { getRuntimeConfig, getBackendUrl, isInitialized } from '../config'
10
- import { tokenStore, refreshToken } from './authClient'
11
-
12
- // ── Guest session management ─────────────────────────────────────────────────
13
-
14
- let _guestSessionId: string | null = null
15
-
16
- export function getOrCreateGuestSessionId(): string {
17
- if (_guestSessionId) return _guestSessionId
18
-
19
- const storageKey = isInitialized()
20
- ? `kasuvia-guest-${getRuntimeConfig().businessId}`
21
- : 'kasuvia-guest-anon'
22
-
23
- if (typeof window !== 'undefined') {
24
- _guestSessionId = sessionStorage.getItem(storageKey) || crypto.randomUUID()
25
- sessionStorage.setItem(storageKey, _guestSessionId)
26
- } else {
27
- _guestSessionId = crypto.randomUUID()
28
- }
29
-
30
- return _guestSessionId
31
- }
32
-
33
- export function getGuestSessionId(): string | null {
34
- if (!_guestSessionId && typeof window !== 'undefined') {
35
- const storageKey = isInitialized()
36
- ? `kasuvia-guest-${getRuntimeConfig().businessId}`
37
- : 'kasuvia-guest-anon'
38
- _guestSessionId = sessionStorage.getItem(storageKey)
39
- }
40
- return _guestSessionId
41
- }
42
-
43
- export function clearGuestSessionId(): void {
44
- _guestSessionId = null
45
- if (typeof window !== 'undefined') {
46
- const storageKey = isInitialized()
47
- ? `kasuvia-guest-${getRuntimeConfig().businessId}`
48
- : 'kasuvia-guest-anon'
49
- sessionStorage.removeItem(storageKey)
50
- }
51
- }
52
-
53
- // ── Store Management Axios ───────────────────────────────────────────────────
54
-
55
- export const storeManagementAxios = axios.create({
56
- baseURL: getBackendUrl('store'),
57
- headers: {
58
- 'Content-Type': 'application/json',
59
- Accept: 'application/json',
60
- },
61
- timeout: 30_000,
62
- }) as AxiosInstance
63
-
64
- storeManagementAxios.interceptors.request.use((cfg) => {
65
- const config = getRuntimeConfig()
66
-
67
- const token = tokenStore.getAccessToken()
68
- if (token) {
69
- cfg.headers = cfg.headers ?? {}
70
- cfg.headers['Authorization'] = `Bearer ${token}`
71
- }
72
-
73
- cfg.headers = cfg.headers ?? {}
74
- cfg.headers['X-Storefront-Key'] = config.storefrontApiKey
75
- cfg.headers['X-Business-ID'] = config.businessId
76
-
77
- if (!token) {
78
- const sid = getOrCreateGuestSessionId()
79
- if (sid) cfg.headers['X-Guest-Session'] = sid
80
- }
81
-
82
- return cfg
83
- })
84
-
85
- storeManagementAxios.interceptors.response.use(
86
- (res) => res,
87
- async (err) => {
88
- const originalRequest = err.config
89
- if (!originalRequest) return Promise.reject(err)
90
-
91
- const status = err.response?.status
92
- const isUnauthorized = status === 401
93
- const isRefreshEndpoint = originalRequest.url?.includes('/auth/token/refresh/')
94
- const alreadyRetried = originalRequest._retry === true
95
-
96
- if (isUnauthorized && isRefreshEndpoint) {
97
- tokenStore.clearAll()
98
- return Promise.reject(err)
99
- }
100
-
101
- if (!isUnauthorized || alreadyRetried) return Promise.reject(err)
102
-
103
- try {
104
- const newToken = await refreshToken()
105
- originalRequest._retry = true
106
- if (originalRequest.headers) {
107
- originalRequest.headers['Authorization'] = `Bearer ${newToken}`
108
- }
109
- return storeManagementAxios(originalRequest)
110
- } catch (refreshErr) {
111
- tokenStore.clearAll()
112
- return Promise.reject(refreshErr)
113
- }
114
- }
115
- )
116
-
117
- // ── Public Store Axios ───────────────────────────────────────────────────────
118
-
119
- export const publicStoreAxios = axios.create({
120
- baseURL: getBackendUrl('store'),
121
- headers: {
122
- 'Content-Type': 'application/json',
123
- Accept: 'application/json',
124
- },
125
- timeout: 30_000,
126
- })
127
-
128
- publicStoreAxios.interceptors.request.use((cfg) => {
129
- const config = getRuntimeConfig()
130
- cfg.headers = cfg.headers ?? {}
131
- cfg.headers['X-Storefront-Key'] = config.storefrontApiKey
132
- cfg.headers['X-Business-ID'] = config.businessId
133
- return cfg
134
- })
135
-
136
- publicStoreAxios.interceptors.response.use(
137
- (res) => res,
138
- (err) => Promise.reject(err)
139
- )
140
-
141
- // ── Auth Axios ───────────────────────────────────────────────────────────────
142
-
143
- export const authAxios = axios.create({
144
- baseURL: getBackendUrl('auth'),
145
- headers: {
146
- 'Content-Type': 'application/json',
147
- Accept: 'application/json',
148
- },
149
- withCredentials: true,
150
- timeout: 30_000,
151
- }) as AxiosInstance
152
-
153
- authAxios.interceptors.request.use((cfg) => {
154
- const config = getRuntimeConfig()
155
-
156
- const token = tokenStore.getAccessToken()
157
- if (token) {
158
- cfg.headers = cfg.headers ?? {}
159
- cfg.headers['Authorization'] = `Bearer ${token}`
160
- }
161
-
162
- cfg.headers = cfg.headers ?? {}
163
- cfg.headers['X-Storefront-Key'] = config.storefrontApiKey
164
- cfg.headers['X-Business-ID'] = config.businessId
165
-
166
- return cfg
167
- })
168
-
169
- authAxios.interceptors.response.use(
170
- (res) => res,
171
- async (err) => {
172
- const originalRequest = err.config
173
- if (!originalRequest) return Promise.reject(err)
174
-
175
- const status = err.response?.status
176
- const isUnauthorized = status === 401
177
- const isRefreshEndpoint = originalRequest.url?.includes('/auth/token/refresh/')
178
- const alreadyRetried = originalRequest._retry === true
179
-
180
- if (isUnauthorized && isRefreshEndpoint) {
181
- tokenStore.clearAll()
182
- return Promise.reject(err)
183
- }
184
-
185
- if (!isUnauthorized || alreadyRetried) return Promise.reject(err)
186
-
187
- try {
188
- const newToken = await refreshToken()
189
- originalRequest._retry = true
190
- if (originalRequest.headers) {
191
- originalRequest.headers['Authorization'] = `Bearer ${newToken}`
192
- }
193
- return authAxios(originalRequest)
194
- } catch (refreshErr) {
195
- tokenStore.clearAll()
196
- return Promise.reject(refreshErr)
197
- }
198
- }
199
- )
200
-
201
- // ── Payment Axios ────────────────────────────────────────────────────────────
202
-
203
- export const paymentAxios = axios.create({
204
- baseURL: getBackendUrl('payment'),
205
- headers: {
206
- 'Content-Type': 'application/json',
207
- Accept: 'application/json',
208
- },
209
- timeout: 30_000,
210
- })
211
-
212
- paymentAxios.interceptors.request.use((cfg) => {
213
- const config = getRuntimeConfig()
214
- cfg.headers = cfg.headers ?? {}
215
- cfg.headers['X-Storefront-Key'] = config.storefrontApiKey
216
- cfg.headers['X-Business-ID'] = config.businessId
217
- return cfg
218
- })
219
-
220
- paymentAxios.interceptors.response.use(
221
- (res) => res,
222
- (err) => Promise.reject(err)
223
- )
224
-
225
- // ── Notifications Axios ──────────────────────────────────────────────────────
226
-
227
- export const notificationsAxios = axios.create({
228
- baseURL: getBackendUrl('notifications'),
229
- headers: {
230
- 'Content-Type': 'application/json',
231
- Accept: 'application/json',
232
- },
233
- timeout: 30_000,
234
- })
235
-
236
- notificationsAxios.interceptors.response.use(
237
- (res) => res,
238
- (err) => Promise.reject(err)
239
- )
240
-
241
- // ── CMS Axios ───────────────────────────────────────────────────────────────
242
-
243
- export const cmsAxios = axios.create({
244
- baseURL: getBackendUrl('cms'),
245
- headers: {
246
- 'Content-Type': 'application/json',
247
- Accept: 'application/json',
248
- },
249
- timeout: 30_000,
250
- })
251
-
252
- cmsAxios.interceptors.request.use((cfg) => {
253
- const config = getRuntimeConfig()
254
- cfg.headers = cfg.headers ?? {}
255
- cfg.headers['X-Storefront-Key'] = config.storefrontApiKey
256
- cfg.headers['X-Business-ID'] = config.businessId
257
- return cfg
258
- })
259
-
260
- cmsAxios.interceptors.response.use(
261
- (res) => res,
262
- (err) => Promise.reject(err)
263
- )
264
-
265
- // ── Refresh Axios ────────────────────────────────────────────────────────────
266
-
267
- export const refreshAxios = axios.create({
268
- baseURL: getBackendUrl('auth'),
269
- headers: {
270
- 'Content-Type': 'application/json',
271
- Accept: 'application/json',
272
- },
273
- withCredentials: true,
274
- timeout: 30_000,
275
- })
276
-
277
- /**
278
- * configureHttpClients()
279
- * Updates the baseURL of all Axios instances to match the configured environment.
280
- * This must be called after initSdk() to ensure the correct environment URLs are used.
281
- */
282
- export function configureHttpClients(): void {
283
- if (!isInitialized()) return
284
-
285
- const storeUrl = getBackendUrl('store')
286
- const authUrl = getBackendUrl('auth')
287
- const paymentUrl = getBackendUrl('payment')
288
- const notificationsUrl = getBackendUrl('notifications')
289
- const cmsUrl = getBackendUrl('cms')
290
-
291
- storeManagementAxios.defaults.baseURL = storeUrl
292
- publicStoreAxios.defaults.baseURL = storeUrl
293
- authAxios.defaults.baseURL = authUrl
294
- paymentAxios.defaults.baseURL = paymentUrl
295
- notificationsAxios.defaults.baseURL = notificationsUrl
296
- cmsAxios.defaults.baseURL = cmsUrl
297
- refreshAxios.defaults.baseURL = authUrl
298
- }