b5-api-client 0.0.22 → 0.0.24

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/types.d.ts CHANGED
@@ -43,6 +43,8 @@ export interface Order {
43
43
  sellerCanDispute?: boolean;
44
44
  buyerDisputed?: boolean;
45
45
  sellerDisputed?: boolean;
46
+ escrowOrder?: EscrowOrderModel;
47
+ dispute?: Dispute[];
46
48
  }
47
49
  export interface OrderResponse {
48
50
  orders: Order[];
@@ -50,6 +52,14 @@ export interface OrderResponse {
50
52
  export interface UsersResponse {
51
53
  users: KioscoinUser[];
52
54
  }
55
+ export interface UserSettings {
56
+ preferredTokenCode?: string;
57
+ paymentMethods: PaymentMethod[];
58
+ }
59
+ export interface UpdateUserSettingsRequest {
60
+ userId: string;
61
+ settings: UserSettings;
62
+ }
53
63
  export interface PaymentMethod {
54
64
  type: string;
55
65
  alias?: string;
@@ -239,6 +249,7 @@ export interface KioscoinUser {
239
249
  timeToRelease?: number;
240
250
  timeToFiatSent?: number;
241
251
  reviews?: UserReview[];
252
+ settings?: UserSettings;
242
253
  }
243
254
  export type DisputeReason = 'SELLER_NOT_LOCKING' | 'BUYER_FIAT_NOT_SENT' | 'SELLER_NOT_RELEASING';
244
255
  export interface CreateDisputeRequest {
@@ -280,3 +291,51 @@ export interface TransactionRequest {
280
291
  offer: OrderOffer;
281
292
  buyerAddress?: string;
282
293
  }
294
+ export type EscrowOrderStatus = 'NON_EXISTING' | 'LOCKED' | 'RELEASED' | 'REFUNDED';
295
+ export interface EscrowOrderModel {
296
+ id: string;
297
+ timestamp?: string;
298
+ tokenContractAddress?: string;
299
+ buyerAddress?: string;
300
+ sellerAddress?: string;
301
+ amount?: string;
302
+ fee?: string;
303
+ status?: EscrowOrderStatus;
304
+ adminAction?: boolean;
305
+ token?: string;
306
+ }
307
+ export interface ExchangeRateOffer {
308
+ id: string;
309
+ tokenCode: string;
310
+ blockchainEnvironment: string;
311
+ fiatCode: string;
312
+ baseFiatRate: string;
313
+ offeredFiatRate: string;
314
+ margin: string;
315
+ validUntil: string;
316
+ isActive: boolean;
317
+ createdAt: string;
318
+ }
319
+ export interface KioscoinOperation {
320
+ orderType: OrderType;
321
+ tokenAmount: string;
322
+ fiatAmount: string;
323
+ tokenCode: string;
324
+ fiatCode: string;
325
+ blockchain: string;
326
+ userId: string;
327
+ username?: string;
328
+ buyerAddress?: string;
329
+ paymentMethods?: PaymentMethod[];
330
+ orderOffer?: OrderOffer;
331
+ exchangeRateOffer?: ExchangeRateOffer;
332
+ }
333
+ export type OperationErrorType = 'INVALID_TRANSACTION' | 'HAS_ACTIVE_ORDERS' | 'INSUFFICIENT_FUNDS' | 'UNKNOWN_ERROR';
334
+ export interface OperationError {
335
+ type: OperationErrorType;
336
+ message: string;
337
+ }
338
+ export interface KioscoinOperationResponse {
339
+ error?: OperationError;
340
+ order?: Order;
341
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "b5-api-client",
3
- "version": "0.0.22",
3
+ "version": "0.0.24",
4
4
  "description": "Escrow Backend API client",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -12,7 +12,8 @@
12
12
  "dependencies": {
13
13
  "axios": "^1.6.7",
14
14
  "https-proxy-agent": "^7.0.5",
15
- "lodash": "^4.17.21"
15
+ "lodash": "^4.17.21",
16
+ "firebase": "11.1.0"
16
17
  },
17
18
  "devDependencies": {
18
19
  "@types/lodash": "^4.17.7",
@@ -0,0 +1,496 @@
1
+ import { initializeApp } from "firebase/app";
2
+ import {
3
+ getAuth,
4
+ signInWithEmailAndPassword,
5
+ createUserWithEmailAndPassword,
6
+ sendEmailVerification,
7
+ sendPasswordResetEmail,
8
+ signOut,
9
+ User,
10
+ GoogleAuthProvider,
11
+ FacebookAuthProvider,
12
+ TwitterAuthProvider,
13
+ signInWithPopup,
14
+ isSignInWithEmailLink,
15
+ sendSignInLinkToEmail,
16
+ signInWithEmailLink,
17
+ Auth
18
+ } from "firebase/auth";
19
+ import { getMessaging, getToken, onMessage, MessagePayload } from "firebase/messaging";
20
+ import { CreateUserRequest, KioscoinUser } from "./types";
21
+ import P2PMarketplaceAPIClient from "./P2PMarketplaceAPIClient";
22
+ import { FirebaseLoginServiceConfig, LoginService } from "./auth/LoginService";
23
+ import { userContext } from './auth/UserContext';
24
+
25
+
26
+ // Auth provider type for better type safety
27
+ export enum AuthProvider {
28
+ EMAIL = 'EMAIL',
29
+ GOOGLE = 'GOOGLE',
30
+ FACEBOOK = 'FACEBOOK',
31
+ TWITTER = 'TWITTER'
32
+ }
33
+
34
+ // Immutable auth state class
35
+ export class AuthState {
36
+ private constructor(
37
+ public readonly isAuthenticated: boolean,
38
+ public readonly user: UserData | null,
39
+ public readonly provider: AuthProvider | null
40
+ ) { }
41
+
42
+ static authenticated(user: UserData, provider: AuthProvider): AuthState {
43
+ return new AuthState(true, user, provider);
44
+ }
45
+
46
+ static unauthenticated(): AuthState {
47
+ return new AuthState(false, null, null);
48
+ }
49
+ }
50
+
51
+ // Enhanced UserData class with additional safety
52
+ export class UserData {
53
+ private constructor(
54
+ public readonly id: string,
55
+ public readonly email: string,
56
+ public readonly username: string,
57
+ public readonly isEmailVerified: boolean,
58
+ public readonly idToken: string | null
59
+ ) { }
60
+
61
+ static create(
62
+ id: string,
63
+ email: string,
64
+ username: string,
65
+ isEmailVerified: boolean,
66
+ idToken: string | null
67
+ ): UserData {
68
+ return new UserData(
69
+ id,
70
+ email,
71
+ username,
72
+ isEmailVerified,
73
+ idToken
74
+ );
75
+ }
76
+
77
+ withEmailVerified(): UserData {
78
+ return new UserData(
79
+ this.id,
80
+ this.email,
81
+ this.username,
82
+ true,
83
+ this.idToken
84
+ );
85
+ }
86
+ }
87
+
88
+ // Enhanced AuthResult class
89
+ export class AuthResult {
90
+ private constructor(
91
+ public readonly success: boolean,
92
+ public readonly user: KioscoinUser | null,
93
+ public readonly userData: UserData | null,
94
+ public readonly error: string | null,
95
+ public readonly provider: AuthProvider | null
96
+ ) { }
97
+
98
+ static success(user: KioscoinUser, userData: UserData, provider: AuthProvider): AuthResult {
99
+ return new AuthResult(true, user, userData, null, provider);
100
+ }
101
+
102
+ static failure(error: string): AuthResult {
103
+ return new AuthResult(false, null, null, error, null);
104
+ }
105
+ }
106
+
107
+ export interface FirebaseConfig {
108
+ apiKey: string;
109
+ authDomain: string;
110
+ projectId: string;
111
+ storageBucket: string;
112
+ messagingSenderId: string;
113
+ appId: string;
114
+ measurementId?: string;
115
+ }
116
+
117
+ export class FirebaseUnifiedService implements LoginService {
118
+ private app: ReturnType<typeof initializeApp> | null = null;
119
+ private messaging: ReturnType<typeof getMessaging> | null = null;
120
+ private auth: Auth | null = null;
121
+ private readonly actionCodeSettings: { url: string; handleCodeInApp: boolean };
122
+ private client: P2PMarketplaceAPIClient;
123
+
124
+ constructor(client: P2PMarketplaceAPIClient, config?: FirebaseLoginServiceConfig) {
125
+ this.client = client;
126
+ this.actionCodeSettings = config?.actionCodeSettings ?? {
127
+ url: 'http://localhost:3000/auth/verify-email',
128
+ handleCodeInApp: true,
129
+ };
130
+ this.initializeFirebase();
131
+ }
132
+
133
+ private async initializeFirebase() {
134
+ try {
135
+ const firebaseConfig = await this.fetchFirebaseConfig();
136
+ this.app = initializeApp(firebaseConfig);
137
+ this.auth = getAuth(this.app);
138
+ } catch (error) {
139
+ console.error("Failed to initialize Firebase app:", error);
140
+ }
141
+ }
142
+
143
+ private async fetchFirebaseConfig(): Promise<FirebaseConfig> {
144
+ try {
145
+ const response = await this.client.getConfig<{ firebase: FirebaseConfig }>();
146
+ return response.config.firebase;
147
+ } catch (error) {
148
+ console.error("Error fetching Firebase configuration:", error);
149
+ throw new Error("Failed to initialize Firebase configuration");
150
+ }
151
+ }
152
+
153
+ // Messaging methods
154
+ public async requestNotificationPermission(): Promise<string | null> {
155
+ if (!this.app) {
156
+ await this.initializeFirebase();
157
+ }
158
+
159
+ if (!this.messaging) {
160
+ console.warn("Messaging is not available");
161
+ return null;
162
+ }
163
+
164
+ try {
165
+ const permission = await Notification.requestPermission();
166
+ if (permission === "granted") {
167
+ const token = await getToken(this.messaging, {
168
+ vapidKey: process.env.REACT_APP_FIREBASE_VAPID_KEY,
169
+ });
170
+ return token;
171
+ }
172
+ return null;
173
+ } catch (error) {
174
+ console.error("Failed to get notification token:", error);
175
+ return null;
176
+ }
177
+ }
178
+ async fetchToken(): Promise<string | null> {
179
+ try {
180
+ if (!this.messaging && this.app) {
181
+ this.messaging = getMessaging(this.app);
182
+ }
183
+ if (this.messaging) {
184
+ const currentToken = await getToken(this.messaging);
185
+ return currentToken;
186
+ }
187
+ return null;
188
+ } catch (error) {
189
+ console.error("An error occurred while retrieving token. ", error);
190
+ return null;
191
+ }
192
+ }
193
+
194
+ onMessageListener(callback: (payload: any) => void) {
195
+ if (!this.messaging && this.app) {
196
+ this.messaging = getMessaging(this.app);
197
+ }
198
+ if (this.messaging) {
199
+ onMessage(this.messaging, callback);
200
+ } else {
201
+ console.error("Messaging is not initialized.");
202
+ }
203
+ }
204
+
205
+ // Authentication methods
206
+ async signInWithEmail(email: string, password: string): Promise<AuthResult> {
207
+ try {
208
+ if (!this.auth) {
209
+ console.error("Firebase Auth not initialized");
210
+ return AuthResult.failure("Firebase Auth not initialized");
211
+ }
212
+ const userCredential = await signInWithEmailAndPassword(this.auth, email, password);
213
+ const idToken = await userCredential.user.getIdToken();
214
+ const backendUser = await this.getUserFromBackend(userCredential.user.uid, idToken);
215
+
216
+ if (backendUser) {
217
+ const authResult = this._createSuccessAuthResult(userCredential.user, backendUser, AuthProvider.EMAIL, idToken);
218
+ // Update UserContext
219
+ userContext.setUser({
220
+ id: userCredential.user.uid,
221
+ username: backendUser.username || '',
222
+ email: userCredential.user.email || '',
223
+ idToken: idToken
224
+ });
225
+ return authResult;
226
+ } else {
227
+ return AuthResult.failure('User not found in backend');
228
+ }
229
+ } catch (error: any) {
230
+ console.error('Detailed error signing in with email:', {
231
+ code: error.code,
232
+ message: error.message,
233
+ fullError: error
234
+ });
235
+ return AuthResult.failure(error.message || 'Authentication failed');
236
+ }
237
+ }
238
+
239
+ async createUserWithEmail(email: string, password: string, username: string): Promise<AuthResult> {
240
+ try {
241
+ if (!this.auth) {
242
+ return AuthResult.failure("Firebase Auth not initialized");
243
+ }
244
+
245
+ // First create the user in Firebase
246
+ const userCredential = await createUserWithEmailAndPassword(this.auth, email, password);
247
+ const user = userCredential.user;
248
+ const idToken = await user.getIdToken();
249
+ // Send email verification
250
+ await sendEmailVerification(user);
251
+
252
+ const backendUser = await this.createUserInBackend(user.uid, username, idToken);
253
+
254
+ if (backendUser) {
255
+ return this._createSuccessAuthResult(user, backendUser, AuthProvider.EMAIL, idToken);
256
+ } else {
257
+ return AuthResult.failure('Failed to create user in backend');
258
+ }
259
+ } catch (error: any) {
260
+ console.error('Error creating user with email:', error);
261
+ return AuthResult.failure(error.message || 'User creation failed');
262
+ }
263
+ }
264
+
265
+ async signInWithGoogle(): Promise<AuthResult> {
266
+ try {
267
+ if (!this.auth) {
268
+ return AuthResult.failure("Firebase Auth not initialized");
269
+ }
270
+
271
+ const provider = new GoogleAuthProvider();
272
+ const userCredential = await signInWithPopup(this.auth, provider);
273
+ const user = userCredential.user;
274
+
275
+ // Get or create user in your Kotlin backend
276
+ const backendUser = await this.getUserFromBackend(user.uid, user.displayName || '');
277
+ const idToken = await user.getIdToken();
278
+ if (backendUser) {
279
+ // Use the new helper method
280
+ return this._createSuccessAuthResult(user, backendUser, AuthProvider.GOOGLE, idToken);
281
+ } else {
282
+ return AuthResult.failure('Failed to get or create user in backend');
283
+ }
284
+ } catch (error: any) {
285
+ console.error('Error signing in with Google:', error);
286
+ return AuthResult.failure(error.message || 'Google authentication failed');
287
+ }
288
+ }
289
+
290
+ async signInWithFacebook(): Promise<AuthResult> {
291
+ try {
292
+ if (!this.auth) {
293
+ return AuthResult.failure("Firebase Auth not initialized");
294
+ }
295
+
296
+ const provider = new FacebookAuthProvider();
297
+ const userCredential = await signInWithPopup(this.auth, provider);
298
+ const user = userCredential.user;
299
+
300
+ // Get or create user in your Kotlin backend
301
+ const backendUser = await this.getUserFromBackend(user.uid, user.displayName || '');
302
+ const idToken = await user.getIdToken();
303
+ if (backendUser) {
304
+ // Use the new helper method
305
+ return this._createSuccessAuthResult(user, backendUser, AuthProvider.FACEBOOK, idToken);
306
+ } else {
307
+ return AuthResult.failure('Failed to get or create user in backend');
308
+ }
309
+ } catch (error: any) {
310
+ console.error('Error signing in with Facebook:', error);
311
+ return AuthResult.failure(error.message || 'Facebook authentication failed');
312
+ }
313
+ }
314
+
315
+ async signInWithTwitter(): Promise<AuthResult> {
316
+ try {
317
+ if (!this.auth) {
318
+ return AuthResult.failure("Firebase Auth not initialized");
319
+ }
320
+
321
+ const provider = new TwitterAuthProvider();
322
+ const userCredential = await signInWithPopup(this.auth, provider);
323
+ const user = userCredential.user;
324
+
325
+ // Get or create user in your Kotlin backend
326
+ const backendUser = await this.getUserFromBackend(user.uid, user.displayName || '');
327
+ const idToken = await user.getIdToken();
328
+ if (backendUser) {
329
+ // Use the new helper method
330
+ return this._createSuccessAuthResult(user, backendUser, AuthProvider.TWITTER, idToken);
331
+ } else {
332
+ return AuthResult.failure('Failed to get or create user in backend');
333
+ }
334
+ } catch (error: any) {
335
+ console.error('Error signing in with Twitter:', error);
336
+ return AuthResult.failure(error.message || 'Twitter authentication failed');
337
+ }
338
+ }
339
+
340
+ async sendEmailVerification(): Promise<boolean> {
341
+ try {
342
+ if (!this.auth) {
343
+ return false;
344
+ }
345
+
346
+ const user = this.auth.currentUser;
347
+ if (user) {
348
+ await sendEmailVerification(user);
349
+ return true;
350
+ }
351
+ return false;
352
+ } catch (error) {
353
+ console.error('Error sending email verification:', error);
354
+ return false;
355
+ }
356
+ }
357
+
358
+ async sendPasswordResetEmail(email: string): Promise<boolean> {
359
+ try {
360
+ if (!this.auth) {
361
+ return false;
362
+ }
363
+
364
+ await sendPasswordResetEmail(this.auth, email);
365
+ return true;
366
+ } catch (error) {
367
+ console.error('Error sending password reset email:', error);
368
+ return false;
369
+ }
370
+ }
371
+
372
+ async signOut(): Promise<void> {
373
+ try {
374
+ if (!this.auth) {
375
+ return;
376
+ }
377
+
378
+ await signOut(this.auth);
379
+ // Clear UserContext
380
+ userContext.clearUser();
381
+ } catch (error) {
382
+ console.error('Error signing out:', error);
383
+ }
384
+ }
385
+
386
+ getCurrentUser(): User | null {
387
+ if (!this.auth) {
388
+ return null;
389
+ }
390
+ return this.auth.currentUser;
391
+ }
392
+
393
+ async sendSignInLinkToEmail(email: string): Promise<boolean> {
394
+ try {
395
+ if (!this.auth) {
396
+ return false;
397
+ }
398
+
399
+ await sendSignInLinkToEmail(this.auth, email, this.actionCodeSettings);
400
+ // Save the email locally to use it later when the user clicks the link
401
+ window.localStorage.setItem('emailForSignIn', email);
402
+ return true;
403
+ } catch (error: any) {
404
+ console.error('Error sending sign-in link to email:', error);
405
+ return false;
406
+ }
407
+ }
408
+
409
+ isSignInWithEmailLink(): boolean {
410
+ if (!this.auth) {
411
+ return false;
412
+ }
413
+ return isSignInWithEmailLink(this.auth, window.location.href);
414
+ }
415
+
416
+ async signInWithEmailLink(email: string): Promise<AuthResult> {
417
+ try {
418
+ if (!this.auth) {
419
+ return AuthResult.failure("Firebase Auth not initialized");
420
+ }
421
+
422
+ if (!this.isSignInWithEmailLink()) {
423
+ return AuthResult.failure("Invalid sign-in link");
424
+ }
425
+
426
+ const userCredential = await signInWithEmailLink(this.auth, email, window.location.href);
427
+ const user = userCredential.user;
428
+
429
+ // Clear the email from localStorage
430
+ window.localStorage.removeItem('emailForSignIn');
431
+
432
+ // Get or create user in your Kotlin backend
433
+ const backendUser = await this.getUserFromBackend(user.uid, user.displayName || '');
434
+ const idToken = await user.getIdToken();
435
+ if (backendUser) {
436
+ // Use the new helper method
437
+ return this._createSuccessAuthResult(user, backendUser, AuthProvider.EMAIL, idToken);
438
+ } else {
439
+ return AuthResult.failure('Failed to get or create user in backend');
440
+ }
441
+ } catch (error: any) {
442
+ console.error('Error signing in with email link:', error);
443
+ return AuthResult.failure(error.message || 'Email link authentication failed');
444
+ }
445
+ }
446
+
447
+ getEmailForSignIn(): string | null {
448
+ return window.localStorage.getItem('emailForSignIn');
449
+ }
450
+
451
+ // Helper method to create success AuthResult
452
+ private _createSuccessAuthResult(
453
+ user: User,
454
+ backendUser: KioscoinUser,
455
+ provider: AuthProvider,
456
+ idToken: string
457
+ ): AuthResult {
458
+ const userData = UserData.create(
459
+ user.uid,
460
+ user.email || '',
461
+ backendUser.username || '',
462
+ user.emailVerified,
463
+ idToken
464
+ );
465
+ return AuthResult.success(backendUser, userData, provider);
466
+ }
467
+
468
+ // Backend integration methods
469
+ private async getUserFromBackend(userId: string, loginId: string): Promise<KioscoinUser | null> {
470
+ try {
471
+ const response = await this.client.getUser(userId, {
472
+ Authorization: `Bearer ${loginId}`,
473
+ });
474
+ return response.users[0];
475
+ } catch (error) {
476
+ console.error('Error getting user from backend:', error);
477
+ return null;
478
+ }
479
+ }
480
+
481
+ private async createUserInBackend(userId: string, username: string, idToken: string): Promise<KioscoinUser | null> {
482
+ try {
483
+ const createUserRequest: CreateUserRequest = {
484
+ id: userId,
485
+ username: username
486
+ }
487
+ const response = await this.client.createUser(createUserRequest, {
488
+ Authorization: `Bearer ${idToken}`
489
+ });
490
+ return response.users[0];
491
+ } catch (error) {
492
+ console.error('Error creating user in backend:', error);
493
+ return null;
494
+ }
495
+ }
496
+ }
@@ -1,5 +1,5 @@
1
1
  import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
2
- import { CreateOrderRequest, CreateUserRequest, Order, OrderLockedEvent, OrderResponse, ReleaseOrderEvent as OrderReleasedEvent, TakeOrderRequest, TestEventParams, UpdateOrderRequest, TransactionStatusResponse, RateUserRequest, OrderEventsResponse, PushNotificationsRegisterRequest, ConfigResponse, DashboardMetricsResponse, KioscoinUser, UsersResponse, PaymentMethod, GetOrdersParams, CreateDisputeRequest, DisputesResponse, OrderOffersResponse, TransactionRequest } from './types';
2
+ import { CreateOrderRequest, CreateUserRequest, Order, OrderLockedEvent, OrderResponse, ReleaseOrderEvent as OrderReleasedEvent, TakeOrderRequest, TestEventParams, UpdateOrderRequest, TransactionStatusResponse, RateUserRequest, OrderEventsResponse, PushNotificationsRegisterRequest, ConfigResponse, DashboardMetricsResponse, KioscoinUser, UsersResponse, PaymentMethod, GetOrdersParams, CreateDisputeRequest, DisputesResponse, OrderOffersResponse, TransactionRequest, KioscoinOperationResponse, UpdateUserSettingsRequest } from './types';
3
3
  import { isPlainObject, camelCase, snakeCase, transform } from 'lodash';
4
4
 
5
5
  class P2PMarketplaceAPIClient {
@@ -137,6 +137,11 @@ class P2PMarketplaceAPIClient {
137
137
  return this.post<any>(url, rateUserRequest, headers);
138
138
  }
139
139
 
140
+ public async updateUserSettings(request: UpdateUserSettingsRequest, headers?: Record<string, string>): Promise<UsersResponse> {
141
+ const url = '/api/users/settings';
142
+ return this.post<UsersResponse>(url, request, headers);
143
+ }
144
+
140
145
  public async registerPushToken(registerRequest: PushNotificationsRegisterRequest, headers?: Record<string, string>): Promise<any> {
141
146
  const url = `/api/notifications/register-push`;
142
147
  return this.post<PushNotificationsRegisterRequest>(url, registerRequest, headers);
@@ -162,9 +167,9 @@ class P2PMarketplaceAPIClient {
162
167
  return this.get<OrderOffersResponse>(url, headers);
163
168
  }
164
169
 
165
- public async pointOfSaleTransact(request: TransactionRequest, headers?: Record<string, string>): Promise<any> {
170
+ public async pointOfSaleTransact(request: TransactionRequest, headers?: Record<string, string>): Promise<KioscoinOperationResponse> {
166
171
  const url = '/api/point_of_sale/transact';
167
- return this.post<TransactionRequest>(url, request, headers);
172
+ return this.post<KioscoinOperationResponse>(url, request, headers);
168
173
  }
169
174
 
170
175