@phygitallabs/tapquest-core 4.5.0 → 4.6.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phygitallabs/tapquest-core",
3
- "version": "4.5.0",
3
+ "version": "4.6.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -23,6 +23,7 @@
23
23
  "prepublishOnly": "bun run build"
24
24
  },
25
25
  "dependencies": {
26
+ "@openreplay/tracker": "^16.4.10",
26
27
  "@phygitallabs/achievement": "latest",
27
28
  "@phygitallabs/api-core": "latest",
28
29
  "@phygitallabs/authentication": "latest",
@@ -0,0 +1,36 @@
1
+ import { EnvironmentType } from "../types/common";
2
+
3
+ const FIREBASE_ENV = {
4
+ dev:{
5
+ "apiKey": "AIzaSyBFReJURBrU3hyiVvHHhMKcruiyy3mkEks",
6
+ "authDomain": "phy-nomion-staging.firebaseapp.com",
7
+ "projectId": "phy-nomion-staging",
8
+ "storageBucket": "phy-nomion-staging.appspot.com",
9
+ "messagingSenderId": "445937266499",
10
+ "appId": "1:445937266499:web:9ce9b49c13dcecd9b889f6",
11
+ "measurementId": "G-GWYSS45K15"
12
+ },
13
+ staging:{
14
+ "apiKey": "AIzaSyAXrJB8n4eZyq43kb9pUSelz9vfkJLHRK8",
15
+ "authDomain": "phygital-388705.firebaseapp.com",
16
+ "projectId": "phygital-388705",
17
+ "storageBucket": "assets-fygito",
18
+ "messagingSenderId": "174350081236",
19
+ "appId": "1:174350081236:web:da29144bf47cf775a5af41",
20
+ "measurementId": "G-JVHVWXMDC9"
21
+ },
22
+ production:{
23
+ "apiKey": "AIzaSyAXrJB8n4eZyq43kb9pUSelz9vfkJLHRK8",
24
+ "authDomain": "phygital-388705.firebaseapp.com",
25
+ "projectId": "phygital-388705",
26
+ "storageBucket": "assets-fygito",
27
+ "messagingSenderId": "174350081236",
28
+ "appId": "1:174350081236:web:da29144bf47cf775a5af41",
29
+ "measurementId": "G-JVHVWXMDC9"
30
+ },
31
+ }
32
+
33
+ export const firebaseConfig = (env: EnvironmentType) => {
34
+ return FIREBASE_ENV[env];
35
+ };
36
+
package/src/index.ts CHANGED
@@ -16,6 +16,8 @@ export * from "./modules/generate-certificate";
16
16
 
17
17
  export * from "./modules/data-tracking";
18
18
 
19
+ export * from "./modules/session-replay";
20
+
19
21
  export * from "./providers";
20
22
 
21
23
  export * from "./modules/achivementWithReward";
@@ -23,3 +25,5 @@ export * from "./modules/achivementWithReward";
23
25
  export * from "./modules/send-email";
24
26
 
25
27
  export * from "./helper";
28
+
29
+ export * from "./modules/session-replay";
@@ -75,97 +75,3 @@ export const useManyAchievementProgress = (
75
75
  });
76
76
  };
77
77
 
78
- // export const useManyAchievementWithProgress = (
79
- // params: UseManyAchievementWithProgressParams,
80
- // options?: {
81
- // achievementOptions?: Partial<UseQueryOptions>;
82
- // progressOptions?: Partial<UseQueryOptions>;
83
- // }
84
- // ): UseManyAchievementWithProgressReturn => {
85
- // // Fetch achievements with retry logic
86
- // const achievementsQuery = useManyAchievements(
87
- // {
88
- // ...(params.achievementIds?.length && { ids: params.achievementIds }),
89
- // },
90
- // {
91
- // retry: (failureCount: number, error: Error) => {
92
- // // Retry up to 3 times with exponential backoff
93
- // if (failureCount < 3) {
94
- // console.warn(`Achievement fetch failed, retrying... (${failureCount + 1}/3)`, error);
95
- // return true;
96
- // }
97
- // return false;
98
- // },
99
- // retryDelay: (attemptIndex: number) => Math.min(1000 * 2 ** attemptIndex, 30000),
100
- // staleTime: 5 * 60 * 1000, // 5 minutes
101
- // gcTime: 10 * 60 * 1000, // 10 minutes
102
- // ...options?.achievementOptions,
103
- // }
104
- // );
105
-
106
- // // Prepare achievement IDs for progress query
107
- // const achievementIds = useMemo(() => {
108
- // if (params.achievementIds?.length) {
109
- // return params.achievementIds;
110
- // }
111
- // return achievementsQuery.data?.data?.map((a: any) => a.id) || [];
112
- // }, [params.achievementIds, achievementsQuery.data]);
113
-
114
- // // Fetch progress with retry logic (dependent on achievements)
115
- // const progressQuery = useManyAchievementProgress(
116
- // {
117
- // achievementIds,
118
- // userId: params.userId,
119
- // deviceUid: params.deviceUid,
120
- // applicationId: params.applicationId,
121
- // },
122
- // {
123
- // enabled: achievementIds.length > 0
124
- // }
125
- // );
126
-
127
- // // Combine data with error flags
128
- // const combinedData = useMemo((): AchievementWithProgress[] => {
129
- // const achievements = achievementsQuery.data?.data || [];
130
- // const progressData = progressQuery.data || [];
131
-
132
- // return achievements.map((achievement: any) => {
133
- // const progress = progressData.find((p: any) => p.achievementId === achievement.id) || null;
134
-
135
- // return {
136
- // achievement,
137
- // progress,
138
- // hasAchievementError: !!achievementsQuery.error,
139
- // hasProgressError: !!progressQuery.error,
140
- // };
141
- // });
142
- // }, [achievementsQuery.data, progressQuery.data, achievementsQuery.error, progressQuery.error]);
143
-
144
- // // Refetch functions
145
- // const refetchAchievements = () => {
146
- // console.log("Manually refetching achievements...");
147
- // achievementsQuery.refetch();
148
- // };
149
-
150
- // const refetchProgress = () => {
151
- // console.log("Manually refetching progress...");
152
- // progressQuery.refetch();
153
- // };
154
-
155
- // const refetchAll = () => {
156
- // console.log("Manually refetching all data...");
157
- // Promise.all([achievementsQuery.refetch(), progressQuery.refetch()]);
158
- // };
159
-
160
- // return {
161
- // data: combinedData,
162
- // isLoading: achievementsQuery.isLoading || (achievementIds.length > 0 && progressQuery.isLoading),
163
- // isSuccess: achievementsQuery.isSuccess && (achievementIds.length === 0 || progressQuery.isSuccess),
164
- // achievementsError: achievementsQuery.error as Error | null,
165
- // progressError: progressQuery.error as Error | null,
166
- // refetchAchievements,
167
- // refetchProgress,
168
- // refetchAll,
169
- // };
170
- // };
171
-
@@ -10,7 +10,20 @@ interface GroupRewardData {
10
10
  reward_models: EntityRewardModel[][];
11
11
  }
12
12
 
13
- function buildMappedAchievements(achievements: Achievement[], groupRewardData: GroupRewardData) {
13
+ export interface AchievementWithRewardModel {
14
+ id: string;
15
+ name: string;
16
+ reward_model: EntityRewardModel | null;
17
+ subAchievements: {
18
+ id: string;
19
+ reward_model: EntityRewardModel | null;
20
+ }[];
21
+ }
22
+
23
+ function buildMappedAchievements(
24
+ achievements: Achievement[],
25
+ groupRewardData: GroupRewardData
26
+ ) {
14
27
  if (!groupRewardData?.reward_models) return [];
15
28
 
16
29
  const rewardModels = groupRewardData.reward_models;
@@ -22,9 +35,9 @@ function buildMappedAchievements(achievements: Achievement[], groupRewardData: G
22
35
  achievement.subAchievementIds?.map((subId: string, subIndex: number) => {
23
36
  const reward =
24
37
  rewardModels[
25
- achievementIndex * (achievement.subAchievementIds?.length || 0) +
26
- subIndex +
27
- 1
38
+ achievementIndex * (achievement.subAchievementIds?.length || 0) +
39
+ subIndex +
40
+ 1
28
41
  ]?.[0] || null;
29
42
 
30
43
  return {
@@ -42,7 +55,12 @@ function buildMappedAchievements(achievements: Achievement[], groupRewardData: G
42
55
  });
43
56
  }
44
57
 
45
- const useAchivementPlusRewardModel = ({ campaignId }: UseAchivementPlusRewardModelParams) => {
58
+ const useAchivementPlusRewardModel = ({
59
+ campaignId,
60
+ }: UseAchivementPlusRewardModelParams): {
61
+ mappedAchievements: AchievementWithRewardModel[];
62
+ isLoading: boolean;
63
+ } => {
46
64
  const { data: achievements, isLoading: isLoadingAchievements } =
47
65
  useManyAchievements(
48
66
  {
@@ -59,6 +77,7 @@ const useAchivementPlusRewardModel = ({ campaignId }: UseAchivementPlusRewardMod
59
77
  .map((achievement) => achievement.groupRewardId)
60
78
  .filter((id): id is string => id !== undefined);
61
79
  }, [achievements?.data]);
80
+
62
81
  const {
63
82
  mutate: fetchGroupRewardModels,
64
83
  data: groupRewardModelsData,
@@ -70,10 +89,14 @@ const useAchivementPlusRewardModel = ({ campaignId }: UseAchivementPlusRewardMod
70
89
  }
71
90
  }, [groupRewardIds, fetchGroupRewardModels]);
72
91
 
73
- const mappedAchievements = useMemo(() => {
92
+ const mappedAchievements: AchievementWithRewardModel[] = useMemo(() => {
74
93
  if (!groupRewardModelsData?.data || !achievements?.data) return [];
75
- return buildMappedAchievements(achievements.data, groupRewardModelsData.data);
94
+ return buildMappedAchievements(
95
+ achievements.data,
96
+ groupRewardModelsData.data
97
+ );
76
98
  }, [groupRewardModelsData, achievements?.data]);
99
+
77
100
  return {
78
101
  mappedAchievements,
79
102
  isLoading: isLoadingAchievements || isPendingGroupRewardModels,
@@ -2,9 +2,10 @@ import { useRef, useCallback, useEffect } from "react";
2
2
  import { useAuth } from "../store/authStore";
3
3
  import { tokenStorage } from "@phygitallabs/authentication";
4
4
  import { ALLOWED_ORIGINS } from "../constants";
5
+ import { decodeJWTToken } from "../utils/user";
5
6
 
6
7
  export interface UseGoogleLoginOptions {
7
- onSuccess?: () => void;
8
+ onSuccess?: (userData: any) => void;
8
9
  onError?: (error: string) => void;
9
10
  onPopupBlocked?: () => void;
10
11
  onPopupClosed?: () => void;
@@ -36,9 +37,11 @@ export function useGoogleLogin(options: UseGoogleLoginOptions = {}): UseGoogleLo
36
37
  refreshToken: newToken.data.refreshToken,
37
38
  });
38
39
 
40
+ const userData = decodeJWTToken(newToken.data.idToken);
41
+
39
42
  setIsSignedIn(true);
40
43
  setIsLoading(false);
41
- onSuccess?.();
44
+ onSuccess?.(userData);
42
45
  }
43
46
  } catch (error) {
44
47
  const errorMessage = error instanceof Error ? error.message : "Token refresh failed";
@@ -8,17 +8,12 @@ export function useTokenRefresher() {
8
8
  const handleRefreshToken = async () => {
9
9
  try {
10
10
  const isTokenExpired = tokenStorage.isTokenExpired();
11
+
11
12
  if (!isTokenExpired) {
12
13
  return;
13
14
  }
14
- const newToken = await refreshToken();
15
-
16
- if(newToken.data?.idToken && newToken.data?.refreshToken){
17
- tokenStorage.setTokens({
18
- idToken: newToken.data?.idToken,
19
- refreshToken: newToken.data?.refreshToken,
20
- });
21
- }
15
+
16
+ await refreshToken();
22
17
 
23
18
  } catch (error) {
24
19
  console.error("Failed to refresh token:", error);
@@ -4,7 +4,7 @@ import {
4
4
  tokenStorage,
5
5
  } from "@phygitallabs/authentication";
6
6
  import { useEffect } from "react";
7
- import { useAuthStore } from "../store/authStore";
7
+ import {useAuthStore} from "../store/authStore";
8
8
  import { transformProtoUserData } from "../utils/user";
9
9
  import { userService } from "@phygitallabs/api-core";
10
10
  import { QueryClient } from "@tanstack/react-query";
@@ -70,8 +70,8 @@ const AuthStateManager = () => {
70
70
  let previousIsSignedIn = useAuthStore.getState().isSignedIn;
71
71
 
72
72
  const unsub = useAuthStore.subscribe(
73
- (state: any) => [state.isSignedIn, state.user],
74
- async ([isSignedIn]: [any]) => {
73
+ (state) => [state.isSignedIn, state.user],
74
+ async ([isSignedIn]) => {
75
75
  // Only run when isSignedIn actually changes
76
76
  if (isSignedIn === previousIsSignedIn) {
77
77
  return;
@@ -0,0 +1,290 @@
1
+ // Client-side only imports
2
+ let firebaseApp: any;
3
+ let firebaseAuth: any;
4
+
5
+ // Dynamic imports for client-side only
6
+ const getFirebaseModules = async () => {
7
+ if (typeof window === 'undefined') {
8
+ throw new Error('Firebase can only be used in client environment');
9
+ }
10
+
11
+ if (!firebaseApp || !firebaseAuth) {
12
+ const [
13
+ { initializeApp, getApps },
14
+ {
15
+ getAuth,
16
+ signInWithEmailAndPassword,
17
+ createUserWithEmailAndPassword,
18
+ signInWithPopup,
19
+ GoogleAuthProvider,
20
+ signOut,
21
+ sendPasswordResetEmail,
22
+ sendEmailVerification,
23
+ updatePassword,
24
+ onAuthStateChanged,
25
+ }
26
+ ] = await Promise.all([
27
+ import("firebase/app"),
28
+ import("firebase/auth")
29
+ ]);
30
+
31
+ firebaseApp = { initializeApp, getApps };
32
+ firebaseAuth = {
33
+ getAuth,
34
+ signInWithEmailAndPassword,
35
+ createUserWithEmailAndPassword,
36
+ signInWithPopup,
37
+ GoogleAuthProvider,
38
+ signOut,
39
+ sendPasswordResetEmail,
40
+ sendEmailVerification,
41
+ updatePassword,
42
+ onAuthStateChanged,
43
+ };
44
+ }
45
+
46
+ return { firebaseApp, firebaseAuth };
47
+ };
48
+
49
+ import { AuthService, FirebaseConfig, AuthResponse, UserData, SignInProvider } from "../types";
50
+ import { accessTokenKey, refreshTokenKey } from "../constants";
51
+
52
+ export class FirebaseAuthService implements AuthService {
53
+ private app: any;
54
+ private auth: any;
55
+ private googleProvider: any;
56
+ private config: FirebaseConfig;
57
+ private initialized = false;
58
+
59
+ constructor(config: FirebaseConfig) {
60
+ this.config = config;
61
+ // Initialization will be done lazily in client environment
62
+ }
63
+
64
+ private async ensureInitialized() {
65
+ if (this.initialized) return;
66
+
67
+ const { firebaseApp, firebaseAuth } = await getFirebaseModules();
68
+
69
+ // Initialize Firebase app if not already initialized
70
+ if (!firebaseApp.getApps().length) {
71
+ this.app = firebaseApp.initializeApp(this.config);
72
+ } else {
73
+ this.app = firebaseApp.getApps()[0];
74
+ }
75
+
76
+ this.auth = firebaseAuth.getAuth(this.app);
77
+ this.googleProvider = new firebaseAuth.GoogleAuthProvider();
78
+ this.initialized = true;
79
+ }
80
+
81
+ private transformUserData(user: any): UserData {
82
+ return {
83
+ id: user.uid,
84
+ uid: user.uid,
85
+ userName: user.displayName || "",
86
+ displayName: user.displayName || "",
87
+ email: user.email || "",
88
+ refreshToken: user.refreshToken,
89
+ accessToken: (user as any).accessToken || "",
90
+ exp: (user as any).stsTokenManager?.expirationTime || 0,
91
+ emailVerified: user.emailVerified,
92
+ avatar: user.photoURL || "/images/default-avatar.jpg",
93
+ signInProvider: this.getSignInProvider(user),
94
+ role: undefined,
95
+ scanStatus: false,
96
+ };
97
+ }
98
+
99
+ private getSignInProvider(user: any): SignInProvider {
100
+ const providers = user.providerData.map((p: any) => p.providerId);
101
+ if (providers.includes("google.com")) return "google.com";
102
+ return "password";
103
+ }
104
+
105
+ private translateErrorCode(errorCode: string): string {
106
+ switch (errorCode) {
107
+ case "auth/invalid-email":
108
+ return "Email không hợp lệ";
109
+ case "auth/user-disabled":
110
+ return "Tài khoản đã bị khóa";
111
+ case "auth/wrong-password":
112
+ return "Tài khoản/mật khẩu không đúng";
113
+ case "auth/user-not-found":
114
+ return "Tài khoản/mật khẩu không đúng";
115
+ case "auth/weak-password":
116
+ return "Weak password! Please use stronger password.";
117
+ case "auth/email-already-in-use":
118
+ return "Email đã được sử dụng";
119
+ case "auth/account-exists-with-different-credential":
120
+ return "Tài khoản email đã được sử dụng bởi một phương thức đăng nhập khác";
121
+ case "auth/email-not-verified":
122
+ return "Email chưa được xác thực";
123
+ default:
124
+ return "Đã có lỗi xảy ra";
125
+ }
126
+ }
127
+
128
+ async signInWithEmailAndPassword(email: string, password: string): Promise<AuthResponse> {
129
+ try {
130
+ await this.ensureInitialized();
131
+ const { firebaseAuth } = await getFirebaseModules();
132
+ const signInResponse = await firebaseAuth.signInWithEmailAndPassword(this.auth, email, password);
133
+ const { user } = signInResponse;
134
+ const data = this.transformUserData(user);
135
+
136
+ const { emailVerified } = data;
137
+ if (!emailVerified) {
138
+ await this.signOut();
139
+ localStorage.removeItem("phygital-user-info");
140
+ localStorage.removeItem(accessTokenKey);
141
+ localStorage.removeItem(refreshTokenKey);
142
+ throw new Error("Email is not verified");
143
+ }
144
+
145
+ return {
146
+ errorCode: "",
147
+ data,
148
+ };
149
+ } catch (err: any) {
150
+ const errorCode = this.translateErrorCode(err.code);
151
+ return {
152
+ errorCode,
153
+ data: null,
154
+ };
155
+ }
156
+ }
157
+
158
+ async signInWithGoogle(): Promise<AuthResponse> {
159
+ try {
160
+ await this.ensureInitialized();
161
+ const { firebaseAuth } = await getFirebaseModules();
162
+ const signInResponse = await firebaseAuth.signInWithPopup(this.auth, this.googleProvider);
163
+ const { user } = signInResponse;
164
+ const data = this.transformUserData(user);
165
+
166
+ const { emailVerified } = data;
167
+ if (!emailVerified) {
168
+ await this.signOut();
169
+ throw new Error("Email is not verified");
170
+ }
171
+
172
+ return {
173
+ errorCode: "",
174
+ data,
175
+ };
176
+ } catch (err: any) {
177
+ const errorCode = this.translateErrorCode(err.code);
178
+ return {
179
+ errorCode,
180
+ data: null,
181
+ };
182
+ }
183
+ }
184
+
185
+ async signUp(email: string, password: string): Promise<AuthResponse> {
186
+ try {
187
+ await this.ensureInitialized();
188
+ const { firebaseAuth } = await getFirebaseModules();
189
+ const signUpResponse = await firebaseAuth.createUserWithEmailAndPassword(this.auth, email, password);
190
+ const { user } = signUpResponse;
191
+ const data = this.transformUserData(user);
192
+
193
+ return {
194
+ errorCode: "",
195
+ data,
196
+ };
197
+ } catch (err: any) {
198
+ const errorCode = this.translateErrorCode(err.code);
199
+ return {
200
+ errorCode,
201
+ data: null,
202
+ };
203
+ }
204
+ }
205
+
206
+ async signOut(): Promise<void> {
207
+ try {
208
+ await this.ensureInitialized();
209
+ const { firebaseAuth } = await getFirebaseModules();
210
+ await firebaseAuth.signOut(this.auth);
211
+ // Tracking is now handled through callbacks in the consuming app
212
+ } catch (err) {
213
+ console.log("Firebase signOut error:", err);
214
+ }
215
+ }
216
+
217
+ async sendPasswordResetEmail(email: string): Promise<void> {
218
+ await this.ensureInitialized();
219
+ const { firebaseAuth } = await getFirebaseModules();
220
+ const actionCodeSettings = {
221
+ url: window.location.origin,
222
+ handleCodeInApp: true,
223
+ };
224
+ await firebaseAuth.sendPasswordResetEmail(this.auth, email, actionCodeSettings);
225
+ }
226
+
227
+ async sendEmailVerification(): Promise<void> {
228
+ await this.ensureInitialized();
229
+ const { firebaseAuth } = await getFirebaseModules();
230
+ if (!this.auth.currentUser) {
231
+ throw new Error("No current user");
232
+ }
233
+ const actionCodeSettings = {
234
+ url: window.location.origin,
235
+ handleCodeInApp: true,
236
+ };
237
+ await firebaseAuth.sendEmailVerification(this.auth.currentUser, actionCodeSettings);
238
+ }
239
+
240
+ async changePassword(newPassword: string): Promise<void> {
241
+ await this.ensureInitialized();
242
+ const { firebaseAuth } = await getFirebaseModules();
243
+ const user = this.auth.currentUser;
244
+ if (!user) {
245
+ throw new Error("No current user");
246
+ }
247
+ return firebaseAuth.updatePassword(user, newPassword);
248
+ }
249
+
250
+ onAuthStateChanged(callback: (user: UserData | null) => void): () => void {
251
+ // For client-side only usage, ensure initialization before setting up auth state listener
252
+ if (typeof window === 'undefined') {
253
+ return () => { }; // Return empty unsubscriber for server-side
254
+ }
255
+
256
+ // Set up the listener asynchronously
257
+ let unsubscriber: (() => void) | null = null;
258
+
259
+ this.ensureInitialized().then(async () => {
260
+ const { firebaseAuth } = await getFirebaseModules();
261
+ unsubscriber = firebaseAuth.onAuthStateChanged(this.auth, (user: any) => {
262
+ if (user && user.emailVerified) {
263
+ const userData = this.transformUserData(user);
264
+ callback(userData);
265
+ } else {
266
+ callback(null);
267
+ }
268
+ });
269
+ });
270
+
271
+ // Return unsubscriber function
272
+ return () => {
273
+ if (unsubscriber) {
274
+ unsubscriber();
275
+ }
276
+ };
277
+ }
278
+
279
+ getCurrentUser(): UserData | null {
280
+ if (typeof window === 'undefined' || !this.initialized) {
281
+ return null; // Return null for server-side or before initialization
282
+ }
283
+
284
+ const user = this.auth.currentUser;
285
+ if (user && user.emailVerified) {
286
+ return this.transformUserData(user);
287
+ }
288
+ return null;
289
+ }
290
+ }
@@ -0,0 +1,22 @@
1
+ import { FirebaseAuthService } from "./FirebaseAuthService";
2
+
3
+ interface AuthServiceConfig {
4
+ firebaseConfig: any;
5
+ }
6
+
7
+ let authServiceInstance: FirebaseAuthService | null = null;
8
+
9
+ export const createAuthService = (config: AuthServiceConfig): FirebaseAuthService => {
10
+ if (!authServiceInstance) {
11
+ authServiceInstance = new FirebaseAuthService(config.firebaseConfig);
12
+ }
13
+ return authServiceInstance;
14
+ };
15
+
16
+ export const getAuthService = (): FirebaseAuthService | null => {
17
+ return authServiceInstance;
18
+ };
19
+
20
+ export const resetAuthService = (): void => {
21
+ authServiceInstance = null;
22
+ };
@@ -0,0 +1,3 @@
1
+ // Export Firebase auth service separately to avoid loading Firebase modules during SSR
2
+ export { FirebaseAuthService } from './FirebaseAuthService';
3
+ export { createAuthService, getAuthService, resetAuthService } from './authServiceFactory';