1
- import { type AuthMiddlewareOptions , ClientBuilder , type HttpMiddlewareOptions } from '@commercetools/ts-client'
1
+ import { ClientBuilder , type HttpMiddlewareOptions } from '@commercetools/ts-client'
2
2
import {
3
+ type ByProjectKeyMeRequestBuilder ,
4
+ type ApiRequest ,
3
5
type ByProjectKeyRequestBuilder ,
4
6
type ClientResponse ,
5
7
createApiBuilderFromCtpClient ,
6
8
type Customer ,
7
9
type CustomerSignInResult
8
10
} from '@commercetools/platform-sdk'
9
- import { SessionStorageTokenCache } from '~/api/TokenCache'
11
+ import { LocalStorageTokenCache } from '~/api/TokenCache'
10
12
11
- type ApiClientProperties = {
13
+ type ApiClientProps = {
12
14
authUri ?: string
13
15
baseUri ?: string
14
16
clientId ?: string
@@ -42,6 +44,8 @@ type SignupPayload = {
42
44
password : string
43
45
}
44
46
47
+ export const LANG = 'en-US'
48
+
45
49
export class CtpApiClient {
46
50
private readonly authUri : string
47
51
private readonly baseUri : string
@@ -50,9 +54,12 @@ export class CtpApiClient {
50
54
private readonly projectKey : string
51
55
private readonly scopes : string
52
56
53
- private readonly tokenCache = new SessionStorageTokenCache ( 'token' )
57
+ private readonly publicTokenCache = new LocalStorageTokenCache ( 'public' )
58
+ private readonly protectedTokenCache = new LocalStorageTokenCache ( 'protected' )
59
+
60
+ private readonly anonymousIdStorageKey = 'anonymous_id'
54
61
55
- private readonly public : ByProjectKeyRequestBuilder
62
+ private public : ByProjectKeyRequestBuilder
56
63
private protected ?: ByProjectKeyRequestBuilder
57
64
private current : ByProjectKeyRequestBuilder
58
65
@@ -63,7 +70,7 @@ export class CtpApiClient {
63
70
clientSecret = String ( import . meta. env . VITE_CTP_CLIENT_SECRET ) ,
64
71
projectKey = String ( import . meta. env . VITE_CTP_PROJECT_KEY ) ,
65
72
scopes = String ( import . meta. env . VITE_CTP_SCOPES )
66
- } : ApiClientProperties = { } ) {
73
+ } : ApiClientProps = { } ) {
67
74
this . authUri = authUri
68
75
this . baseUri = baseUri
69
76
this . clientId = clientId
@@ -74,7 +81,7 @@ export class CtpApiClient {
74
81
this . public = this . createPublic ( )
75
82
76
83
if ( this . hasToken ) {
77
- this . protected = this . createPublic ( true )
84
+ this . protected = this . createProtectedWithToken ( )
78
85
this . current = this . protected
79
86
} else {
80
87
this . current = this . public
@@ -87,62 +94,101 @@ export class CtpApiClient {
87
94
88
95
public get hasToken ( ) : boolean {
89
96
try {
90
- return this . tokenCache . get ( ) . token !== ''
97
+ return this . protectedTokenCache . get ( ) . token !== ''
91
98
} catch {
92
99
return false
93
100
}
94
101
}
95
102
103
+ private static isAnonymousIdError ( error : unknown ) : boolean {
104
+ return (
105
+ typeof error === 'object' &&
106
+ error !== null &&
107
+ 'statusCode' in error &&
108
+ error . statusCode === 400 &&
109
+ 'message' in error &&
110
+ typeof error . message === 'string' &&
111
+ error . message . includes ( 'anonymousId' )
112
+ )
113
+ }
114
+
96
115
public async login ( email : string , password : string ) : Promise < ClientResponse < Customer > > {
97
- this . logout ( )
116
+ const request : ( ) => ApiRequest < CustomerSignInResult > = ( ) =>
117
+ this . public
118
+ . me ( )
119
+ . login ( )
120
+ . post ( {
121
+ body : {
122
+ email,
123
+ password,
124
+ activeCartSignInMode : 'UseAsNewActiveCustomerCart' ,
125
+ updateProductData : true
126
+ }
127
+ } )
128
+
129
+ try {
130
+ await request ( ) . execute ( )
131
+ } catch ( error ) {
132
+ await this . handleError < CustomerSignInResult > ( { error, request } )
133
+ }
98
134
99
- this . protected = this . createProtected ( email , password )
135
+ this . protected = this . createProtectedWithCredentials ( email , password )
100
136
this . current = this . protected
101
137
102
138
return await this . getCurrentCustomer ( )
103
139
}
104
140
105
141
public logout ( ) : void {
106
- this . tokenCache . remove ( )
107
- this . current = this . public
142
+ this . protectedTokenCache . remove ( )
143
+ this . publicTokenCache . remove ( )
144
+ this . public = this . createPublic ( )
108
145
this . protected = undefined
146
+ this . current = this . public
147
+ }
148
+
149
+ public getCurrentCustomerBuilder ( ) : ByProjectKeyMeRequestBuilder {
150
+ return this . current . me ( )
109
151
}
110
152
111
153
public async getCurrentCustomer ( ) : Promise < ClientResponse < Customer > > {
112
- return await this . current . me ( ) . get ( ) . execute ( )
154
+ return await this . getCurrentCustomerBuilder ( ) . get ( ) . execute ( )
113
155
}
114
156
115
157
public async signup ( payload : SignupPayload ) : Promise < ClientResponse < CustomerSignInResult > > {
116
- this . logout ( )
117
-
118
158
const billingAddressIndex = payload . addresses . findIndex ( ( { type } ) => type === CUSTOMER_ADDRESS_TYPE . BILLING )
119
159
const shippingAddressIndex = payload . addresses . findIndex ( ( { type } ) => type === CUSTOMER_ADDRESS_TYPE . SHIPPING )
120
160
121
- return this . current
122
- . me ( )
123
- . signup ( )
124
- . post ( {
125
- body : {
126
- addresses : payload . addresses . map (
127
- ( address ) : Omit < CustomerAddress , 'type' > => ( {
128
- city : address . city ,
129
- country : address . country ,
130
- firstName : payload . firstName ,
131
- lastName : payload . lastName ,
132
- postalCode : address . postalCode ,
133
- streetName : address . streetName
134
- } )
135
- ) ,
136
- dateOfBirth : payload . dateOfBirth ,
137
- defaultBillingAddress : billingAddressIndex === - 1 ? undefined : billingAddressIndex ,
138
- defaultShippingAddress : shippingAddressIndex === - 1 ? undefined : shippingAddressIndex ,
139
- email : payload . email ,
140
- firstName : payload . firstName ,
141
- lastName : payload . lastName ,
142
- password : payload . password
143
- }
144
- } )
145
- . execute ( )
161
+ const request : ( ) => ApiRequest < CustomerSignInResult > = ( ) =>
162
+ this . public
163
+ . me ( )
164
+ . signup ( )
165
+ . post ( {
166
+ body : {
167
+ addresses : payload . addresses . map (
168
+ ( address ) : Omit < CustomerAddress , 'type' > => ( {
169
+ city : address . city ,
170
+ country : address . country ,
171
+ firstName : payload . firstName ,
172
+ lastName : payload . lastName ,
173
+ postalCode : address . postalCode ,
174
+ streetName : address . streetName
175
+ } )
176
+ ) ,
177
+ dateOfBirth : payload . dateOfBirth ,
178
+ defaultBillingAddress : billingAddressIndex === - 1 ? undefined : billingAddressIndex ,
179
+ defaultShippingAddress : shippingAddressIndex === - 1 ? undefined : shippingAddressIndex ,
180
+ email : payload . email ,
181
+ firstName : payload . firstName ,
182
+ lastName : payload . lastName ,
183
+ password : payload . password
184
+ }
185
+ } )
186
+
187
+ try {
188
+ return await request ( ) . execute ( )
189
+ } catch ( error ) {
190
+ return await this . handleError < CustomerSignInResult > ( { error, request } )
191
+ }
146
192
}
147
193
148
194
private getHttpOptions ( ) : HttpMiddlewareOptions {
@@ -154,22 +200,23 @@ export class CtpApiClient {
154
200
}
155
201
}
156
202
157
- private createPublic ( withTokenCache : boolean = false ) : ByProjectKeyRequestBuilder {
158
- const authOptions : AuthMiddlewareOptions = {
159
- credentials : { clientId : this . clientId , clientSecret : this . clientSecret } ,
160
- host : this . authUri ,
161
- httpClient : fetch ,
162
- projectKey : this . projectKey ,
163
- scopes : [ this . scopes ]
164
- }
165
-
166
- if ( withTokenCache ) {
167
- authOptions . tokenCache = this . tokenCache
168
- }
203
+ private createPublic ( ) : ByProjectKeyRequestBuilder {
204
+ const anonymousId = this . getOrCreateAnonymousId ( )
169
205
170
206
const client = new ClientBuilder ( )
171
207
. withProjectKey ( this . projectKey )
172
- . withClientCredentialsFlow ( authOptions )
208
+ . withAnonymousSessionFlow ( {
209
+ credentials : {
210
+ clientId : this . clientId ,
211
+ clientSecret : this . clientSecret ,
212
+ anonymousId
213
+ } ,
214
+ host : this . authUri ,
215
+ httpClient : fetch ,
216
+ projectKey : this . projectKey ,
217
+ scopes : [ this . scopes ] ,
218
+ tokenCache : this . publicTokenCache
219
+ } )
173
220
. withHttpMiddleware ( this . getHttpOptions ( ) )
174
221
. withLoggerMiddleware ( )
175
222
. build ( )
@@ -179,7 +226,7 @@ export class CtpApiClient {
179
226
} )
180
227
}
181
228
182
- private createProtected ( email : string , password : string ) : ByProjectKeyRequestBuilder {
229
+ private createProtectedWithCredentials ( email : string , password : string ) : ByProjectKeyRequestBuilder {
183
230
const client = new ClientBuilder ( )
184
231
. withProjectKey ( this . projectKey )
185
232
. withPasswordFlow ( {
@@ -188,7 +235,33 @@ export class CtpApiClient {
188
235
httpClient : fetch ,
189
236
projectKey : this . projectKey ,
190
237
scopes : [ this . scopes ] ,
191
- tokenCache : this . tokenCache
238
+ tokenCache : this . protectedTokenCache
239
+ } )
240
+ . withHttpMiddleware ( this . getHttpOptions ( ) )
241
+ . withLoggerMiddleware ( )
242
+ . build ( )
243
+
244
+ return createApiBuilderFromCtpClient ( client ) . withProjectKey ( {
245
+ projectKey : this . projectKey
246
+ } )
247
+ }
248
+
249
+ private createProtectedWithToken ( ) : ByProjectKeyRequestBuilder {
250
+ const { refreshToken } = this . protectedTokenCache . get ( )
251
+
252
+ if ( refreshToken === undefined ) {
253
+ throw new Error ( 'Refresh token is missing' )
254
+ }
255
+
256
+ const client = new ClientBuilder ( )
257
+ . withProjectKey ( this . projectKey )
258
+ . withRefreshTokenFlow ( {
259
+ credentials : { clientId : this . clientId , clientSecret : this . clientSecret } ,
260
+ host : this . authUri ,
261
+ httpClient : fetch ,
262
+ projectKey : this . projectKey ,
263
+ tokenCache : this . protectedTokenCache ,
264
+ refreshToken
192
265
} )
193
266
. withHttpMiddleware ( this . getHttpOptions ( ) )
194
267
. withLoggerMiddleware ( )
@@ -198,6 +271,42 @@ export class CtpApiClient {
198
271
projectKey : this . projectKey
199
272
} )
200
273
}
274
+
275
+ private async handleError < T > ( {
276
+ error,
277
+ request
278
+ } : {
279
+ error : unknown
280
+ request : ( ) => ApiRequest < T >
281
+ } ) : Promise < ClientResponse < T > > {
282
+ if ( ! CtpApiClient . isAnonymousIdError ( error ) ) {
283
+ throw error
284
+ }
285
+
286
+ console . log ( 'Anonymous ID is already in use. Creating new anonymous ID...' )
287
+
288
+ this . logout ( )
289
+ return await request ( ) . execute ( )
290
+ }
291
+
292
+ private getOrCreateAnonymousId ( ) : string {
293
+ let id = this . getAnonymousIdFromStorage ( )
294
+
295
+ if ( id === null || ! this . hasToken ) {
296
+ id = crypto . randomUUID ( )
297
+ this . saveAnonymousIdToStorage ( id )
298
+ }
299
+
300
+ return id
301
+ }
302
+
303
+ private getAnonymousIdFromStorage ( ) : string | null {
304
+ return localStorage . getItem ( this . anonymousIdStorageKey )
305
+ }
306
+
307
+ private saveAnonymousIdToStorage ( id : string ) : void {
308
+ localStorage . setItem ( this . anonymousIdStorageKey , id )
309
+ }
201
310
}
202
311
203
312
export const ctpApiClient = new CtpApiClient ( )
0 commit comments