@phygitallabs/tapquest-core 2.7.0 → 2.9.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.
Files changed (35) hide show
  1. package/README.md +1 -1
  2. package/dist/index.cjs +1035 -750
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +29 -35
  5. package/dist/index.d.ts +29 -35
  6. package/dist/index.js +1017 -717
  7. package/dist/index.js.map +1 -1
  8. package/package.json +10 -3
  9. package/src/modules/auth/constants/index.ts +6 -0
  10. package/src/modules/auth/helpers/index.ts +1 -4
  11. package/src/modules/auth/hooks/index.ts +2 -0
  12. package/src/modules/auth/hooks/useGoogleLogin.ts +169 -0
  13. package/src/modules/auth/hooks/useTokenRefresher.ts +39 -0
  14. package/src/modules/auth/index.ts +8 -2
  15. package/src/modules/auth/providers/AuthProvider.tsx +214 -186
  16. package/src/modules/auth/store/authStore.ts +577 -0
  17. package/src/modules/auth/types/auth.ts +29 -0
  18. package/src/modules/auth/types/user-data.ts +38 -0
  19. package/src/modules/auth/utils/user.ts +21 -0
  20. package/src/modules/data-tracking/hooks/index.ts +25 -1
  21. package/src/modules/generate-certificate/helpers/index.ts +3 -0
  22. package/src/modules/generate-certificate/hooks/index.ts +15 -6
  23. package/src/modules/generate-certificate/index.ts +3 -1
  24. package/src/modules/notification/providers/index.tsx +3 -3
  25. package/src/modules/reward/hooks/useRewardService.ts +6 -6
  26. package/src/modules/session-replay/README.md +334 -0
  27. package/src/modules/session-replay/hooks/useSessionReplay.ts +16 -0
  28. package/src/modules/session-replay/index.ts +10 -0
  29. package/src/modules/session-replay/providers/SessionReplayProvider.tsx +189 -0
  30. package/src/modules/session-replay/types/index.ts +147 -0
  31. package/src/modules/session-replay/utils/index.ts +12 -0
  32. package/src/providers/ServicesProvider.tsx +4 -76
  33. package/src/providers/TapquestCoreProvider.tsx +31 -36
  34. package/src/modules/auth/helpers/refreshToken.ts +0 -63
  35. package/src/modules/auth/store/authSlice.ts +0 -137
@@ -0,0 +1,147 @@
1
+ /**
2
+ * OpenReplay Session Replay Types
3
+ *
4
+ * Type definitions for OpenReplay session replay configuration and options
5
+ */
6
+
7
+ /**
8
+ * OpenReplay Configuration Options
9
+ */
10
+ export interface OpenReplayConfig {
11
+ /**
12
+ * OpenReplay project key (required)
13
+ * Get this from your OpenReplay dashboard
14
+ */
15
+ projectKey?: string;
16
+
17
+ /**
18
+ * Custom ingest endpoint URL (optional)
19
+ * For self-hosted OpenReplay instances
20
+ */
21
+ ingestPoint?: string;
22
+
23
+ /**
24
+ * Enable user ID tracking (optional)
25
+ * When enabled, the tracker will automatically set user IDs
26
+ * @default false
27
+ */
28
+ userIdEnabled?: boolean;
29
+
30
+ /**
31
+ * Custom function to get user ID (optional)
32
+ * If not provided and userIdEnabled is true, a UUID will be generated
33
+ */
34
+ getUserId?: () => string;
35
+
36
+ /**
37
+ * Enable debug mode (optional)
38
+ * Shows debug logs in console when enabled
39
+ * @default false
40
+ */
41
+ debug?: boolean;
42
+
43
+ /**
44
+ * Capture exceptions automatically (optional)
45
+ * @default true
46
+ */
47
+ captureExceptions?: boolean;
48
+
49
+ /**
50
+ * Capture performance metrics (optional)
51
+ * @default true
52
+ */
53
+ capturePerformance?: boolean;
54
+
55
+ /**
56
+ * Network tracking options (optional)
57
+ */
58
+ network?: {
59
+ /**
60
+ * Capture network payload data
61
+ * @default true
62
+ */
63
+ capturePayload?: boolean;
64
+
65
+ /**
66
+ * Sanitizer function for network data
67
+ * Use this to redact sensitive information
68
+ */
69
+ sanitizer?: (data: any) => any;
70
+ };
71
+
72
+ /**
73
+ * Console log tracking options (optional)
74
+ */
75
+ console?: {
76
+ /**
77
+ * Console log levels to capture
78
+ * @default ["error", "warn", "log"]
79
+ */
80
+ levels?: Array<"error" | "warn" | "log" | "info" | "debug">;
81
+ };
82
+
83
+ /**
84
+ * Privacy settings
85
+ */
86
+ obscureTextEmails?: boolean;
87
+ obscureTextNumbers?: boolean;
88
+ obscureInputEmails?: boolean;
89
+
90
+ /**
91
+ * Disable secure mode (for development)
92
+ * @default false in production, true in development
93
+ */
94
+ __DISABLE_SECURE_MODE?: boolean;
95
+ }
96
+
97
+ /**
98
+ * Session Replay Provider Props
99
+ */
100
+ export interface SessionReplayProviderProps {
101
+ children: React.ReactNode;
102
+ config?: OpenReplayConfig;
103
+ }
104
+
105
+ /**
106
+ * Tracker State
107
+ * Internal state for the tracker context reducer
108
+ */
109
+ export interface TrackerState {
110
+ tracker: any | null;
111
+ config: OpenReplayConfig;
112
+ }
113
+
114
+ /**
115
+ * Tracker Actions
116
+ */
117
+ export type TrackerAction =
118
+ | { type: "init" }
119
+ | { type: "start" }
120
+ | { type: "setUserId"; payload: string }
121
+ | { type: "setMetadata"; payload: Record<string, any> };
122
+
123
+ /**
124
+ * Tracker Context Value
125
+ * Methods available through the TrackerContext
126
+ */
127
+ export interface TrackerContextValue {
128
+ /**
129
+ * Initialize the OpenReplay tracker
130
+ * Must be called before startTracking
131
+ */
132
+ initTracker: () => void;
133
+
134
+ /**
135
+ * Start tracking the session
136
+ * Must call initTracker first
137
+ */
138
+ startTracking: () => void;
139
+
140
+ /**
141
+ * Set or update the user ID for the current session
142
+ * @param userId - The user ID to set
143
+ */
144
+ setUserId: (userId: string) => void;
145
+
146
+ setMetadata: (metadata: Record<string, any>) => void;
147
+ }
@@ -0,0 +1,12 @@
1
+ import { getDeviceUid } from "../../auth/helpers";
2
+
3
+ export const getSessionUserId = (userId?: string): string | null => {
4
+ if (userId) {
5
+ return userId;
6
+ }
7
+ return getDeviceUid();
8
+ };
9
+
10
+ export const isBrowser = (): boolean => {
11
+ return typeof window !== "undefined";
12
+ };
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useState, useMemo } from "react";
1
+ import React, { useEffect, useState } from "react";
2
2
  import { QueryClient } from "@tanstack/react-query";
3
3
  import { PGLCoreServiceProvider } from "@phygitallabs/api-core";
4
4
  import { RewardServiceProvider } from "@phygitallabs/reward";
@@ -8,13 +8,7 @@ import { GenerateCertificateServiceProvider } from "@phygitallabs/generate-certi
8
8
  import serviceApiUrl from "../constants/service";
9
9
  import { APIConfig, ServiceConfig } from "../types/service";
10
10
 
11
- import { checkDeviceUid, getAccessToken, getRetryAttemptsRefreshToken, setRetryAttemptsRefreshToken, createRefreshTokenFunction } from "../modules/auth/helpers";
12
-
13
- import { httpMaxRetries } from "../modules/auth";
14
-
15
- import { useAuth } from "../modules/auth";
16
-
17
- import axios from "axios";
11
+ import { checkDeviceUid } from "../modules/auth/helpers";
18
12
 
19
13
  interface ServicesProviderProps {
20
14
  children: React.ReactNode;
@@ -30,23 +24,10 @@ export const ServicesProvider: React.FC<ServicesProviderProps> = ({
30
24
  environment: "dev",
31
25
  version: "v1"
32
26
  },
33
- firebaseConfig
34
27
  }) => {
35
- const { refreshUser, signOut } = useAuth();
36
28
  const { environment, version } = apiConfig;
37
29
  const [commonServiceConfig, setCommonServiceConfig] = useState<ServiceConfig | null>(null);
38
30
 
39
- // Create memoized refresh token function
40
- const memoizedRefreshToken = useMemo(() => {
41
- if (!firebaseConfig?.apiKey) {
42
- console.warn("Firebase API key not provided, refresh token functionality will not work");
43
- return null;
44
- }
45
- return createRefreshTokenFunction({
46
- firebaseApiKey: firebaseConfig.apiKey
47
- });
48
- }, [firebaseConfig?.apiKey]);
49
-
50
31
  // Init client
51
32
  useEffect(() => {
52
33
  const initClient = async () => {
@@ -55,64 +36,11 @@ export const ServicesProvider: React.FC<ServicesProviderProps> = ({
55
36
 
56
37
  const responseInterceptors = ({
57
38
  onFulfilled: (response: any) => response,
58
- onRejected: async (error: any) => {
59
-
60
- // TODO: Remove
61
- // Feature: use refresh token to get new access token when token expired, have maximum `retry attempts`
62
- const originalRequest = error.config;
63
-
64
- if (error.response?.status === 401 && !originalRequest._retry) {
65
- const retryAttempts = parseInt(
66
- getRetryAttemptsRefreshToken() || "0",
67
- 10
68
- );
69
-
70
- if (retryAttempts >= httpMaxRetries) {
71
- await signOut();
72
- return Promise.reject(error);
73
- }
74
-
75
- setRetryAttemptsRefreshToken(`${retryAttempts + 1}`);
76
- originalRequest._retry = true;
77
-
78
- try {
79
- if (!memoizedRefreshToken) {
80
- await signOut();
81
- return Promise.reject(error);
82
- }
83
-
84
- const result = await memoizedRefreshToken();
85
-
86
- if (result?.accessToken) {
87
- originalRequest.headers[
88
- "Authorization"
89
- ] = `Bearer ${result.accessToken}`;
90
-
91
- setRetryAttemptsRefreshToken("0");
92
-
93
- refreshUser(result);
94
-
95
- return axios(originalRequest);
96
- }
97
- } catch (refreshError) {
98
- console.log("Failed to refresh token:", refreshError);
99
- }
100
- }
101
-
102
- return Promise.reject(error);
103
- },
39
+ onRejected: async (error: any) => Promise.reject(error),
104
40
  })
105
41
 
106
42
  const requestInterceptors = ({
107
- onFulfilled: (config: any) => {
108
- // Feature: set access token to request header
109
- const currentToken = getAccessToken();
110
-
111
- if (currentToken && !config.headers.Authorization) {
112
- config.headers.Authorization = `Bearer ${currentToken}`;
113
- }
114
- return config;
115
- },
43
+ onFulfilled: (config: any) => config,
116
44
  onRejected: (error: any) => Promise.reject(error),
117
45
  })
118
46
 
@@ -1,64 +1,59 @@
1
1
  import React, { useMemo } from "react";
2
- import { Provider } from "react-redux";
3
- import { PersistGate } from "redux-persist/integration/react";
4
2
 
5
3
  import { ServicesProvider } from "./ServicesProvider";
6
4
  import { APIConfig } from "../types/service";
7
5
  import { QueryClient } from "@tanstack/react-query";
8
6
 
9
- import { store, persistor } from "../store";
10
- import { AuthCallbacks } from "../modules/auth/types";
11
- import { AuthProvider } from "../modules/auth/providers";
12
- import { FirebaseConfig } from "../modules/auth/types";
7
+ import { AuthProvider } from "../modules/auth";
8
+ import serviceApiUrl from "../constants/service";
9
+ import { OpenReplayConfig, SessionReplayProvider } from "../modules/session-replay";
13
10
 
14
11
  interface TapquestCoreProviderProps {
15
12
  children: React.ReactNode;
16
13
  queryClient: QueryClient;
17
14
  apiConfig: APIConfig;
18
- firebaseConfig?: FirebaseConfig;
19
- authCallbacks?: AuthCallbacks;
20
15
  }
21
16
 
22
17
  export const TapquestCoreProvider: React.FC<TapquestCoreProviderProps> = ({
23
18
  children,
24
19
  queryClient,
25
20
  apiConfig,
26
- firebaseConfig,
27
- authCallbacks
21
+ // authCallbacks
28
22
  }) => {
23
+ const { environment } = apiConfig;
29
24
 
30
- const internalAuthService = useMemo(() => {
31
- if (firebaseConfig && typeof window !== "undefined") {
32
- try {
33
- // Dynamic import to avoid loading Firebase modules during build
34
- const { createAuthService } = require("../modules/auth/services");
35
- return createAuthService({ firebaseConfig });
36
- } catch (error) {
37
- console.warn("Failed to create auth service from firebase config:", error);
38
- return null;
39
- }
25
+ const sessionReplayConfig: OpenReplayConfig | undefined = useMemo(() => {
26
+ const projectKey = process.env.NEXT_PUBLIC_OPENREPLAY_PROJECT_KEY;
27
+
28
+ // Only configure if project key is provided
29
+ if (!projectKey) {
30
+ return undefined;
40
31
  }
41
32
 
42
- return null;
43
- }, [firebaseConfig]);
33
+ return {
34
+ projectKey,
35
+ ingestPoint: process.env.NEXT_PUBLIC_OPENREPLAY_INGEST_POINT,
36
+ debug: process.env.NODE_ENV === "development",
37
+ captureExceptions: true,
38
+ capturePerformance: true,
39
+ obscureTextEmails: true,
40
+ obscureInputEmails: true,
41
+ };
42
+ }, []);
44
43
 
45
44
  return (
46
- <Provider store={store}>
47
- <PersistGate loading={null} persistor={persistor}>
45
+ <SessionReplayProvider config={sessionReplayConfig}>
46
+ <ServicesProvider
47
+ queryClient={queryClient}
48
+ apiConfig={apiConfig}
49
+ >
48
50
  <AuthProvider
49
- authService={internalAuthService}
50
- authCallbacks={authCallbacks}
51
+ baseURL={`${serviceApiUrl[environment].API_BASE_URL}`}
52
+ queryClient={queryClient}
51
53
  >
52
- <ServicesProvider
53
- queryClient={queryClient}
54
- apiConfig={apiConfig}
55
- firebaseConfig={firebaseConfig}
56
- >
57
- {children}
58
- </ServicesProvider>
54
+ {children}
59
55
  </AuthProvider>
60
-
61
- </PersistGate>
62
- </Provider>
56
+ </ServicesProvider>
57
+ </SessionReplayProvider>
63
58
  );
64
59
  };
@@ -1,63 +0,0 @@
1
- import mem from "mem";
2
- import { setUserInfo, removeUserInfo, getUserInfo, getRefreshToken } from "./index";
3
-
4
- // Internal configuration - managed by tapquest-core
5
- const REFRESH_TOKEN_CONFIG = {
6
- maxAge: 10000, // 10 seconds cache
7
- } as const;
8
-
9
- export interface RefreshTokenConfig {
10
- firebaseApiKey: string;
11
- }
12
-
13
- const createRefreshTokenFunction = (config: RefreshTokenConfig) => {
14
- const refreshTokenFn = async () => {
15
- try {
16
- const session = getUserInfo();
17
- const refreshToken = getRefreshToken();
18
-
19
- if (!refreshToken) {
20
- removeUserInfo();
21
- return;
22
- }
23
-
24
- const params = new URLSearchParams();
25
- params.append("grant_type", "refresh_token");
26
- params.append("refresh_token", refreshToken);
27
-
28
- const fetchData = {
29
- method: "POST",
30
- headers: new Headers({
31
- "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
32
- }),
33
- body: params,
34
- };
35
-
36
- const response = await fetch(
37
- `https://securetoken.googleapis.com/v1/token?key=${config.firebaseApiKey}`,
38
- fetchData
39
- );
40
- const data = await response.json();
41
-
42
- if (data.error) {
43
- removeUserInfo();
44
- return;
45
- }
46
-
47
- const newSession = {
48
- ...session,
49
- accessToken: data.access_token,
50
- refreshToken: data.refresh_token,
51
- };
52
- setUserInfo(newSession);
53
-
54
- return newSession;
55
- } catch {
56
- removeUserInfo();
57
- }
58
- };
59
-
60
- return mem(refreshTokenFn, { maxAge: REFRESH_TOKEN_CONFIG.maxAge });
61
- };
62
-
63
- export { createRefreshTokenFunction };
@@ -1,137 +0,0 @@
1
- import { createSlice, PayloadAction } from "@reduxjs/toolkit";
2
- import { UserData, UserRole } from "../types";
3
- import {
4
- setUserInfo,
5
- removeUserInfo,
6
- getUserInfo,
7
- setAccessToken,
8
- setRefreshToken,
9
- removeAccessToken,
10
- removeRefreshToken,
11
- getAccessToken,
12
- } from "../helpers";
13
-
14
- const defaultUser: UserData = {
15
- uid: "",
16
- id: "",
17
- userName: "",
18
- displayName: "",
19
- avatar: "/images/default-avatar.jpg",
20
- email: "",
21
- exp: 0,
22
- emailVerified: false,
23
- refreshToken: "",
24
- accessToken: "",
25
- role: UserRole.NULL,
26
- scanStatus: false,
27
- };
28
-
29
- export interface AuthState {
30
- user: UserData;
31
- isSignedIn: boolean;
32
- pending: boolean;
33
- }
34
-
35
- const initialState: AuthState = {
36
- user: defaultUser,
37
- isSignedIn: false,
38
- pending: true,
39
- };
40
-
41
- export const authSlice = createSlice<
42
- AuthState,
43
- {
44
- signIn: (state: AuthState, action: PayloadAction<UserData>) => void;
45
- signOut: (state: AuthState) => void;
46
- updateScanStatus: (state: AuthState, action: PayloadAction<boolean>) => void;
47
- refreshUser: (state: AuthState, action: PayloadAction<UserData>) => void;
48
- setPending: (state: AuthState, action: PayloadAction<boolean>) => void;
49
- initializeFromStorage: (state: AuthState) => void;
50
- },
51
- 'auth'
52
- >({
53
- name: "auth",
54
- initialState,
55
- reducers: {
56
- signIn: (state, action: PayloadAction<UserData>) => {
57
- const userData = action.payload;
58
- state.user = userData;
59
- state.isSignedIn = true;
60
- state.pending = false;
61
-
62
- // Store in localStorage for persistence
63
- setUserInfo(userData);
64
- setAccessToken(userData.accessToken);
65
- setRefreshToken(userData.refreshToken);
66
- },
67
- signOut: (state) => {
68
- state.user = defaultUser;
69
- state.isSignedIn = false;
70
- state.pending = false;
71
-
72
- // Clear localStorage
73
- removeUserInfo();
74
- removeAccessToken();
75
- removeRefreshToken();
76
- },
77
- updateScanStatus: (state, action: PayloadAction<boolean>) => {
78
- state.user.scanStatus = action.payload;
79
-
80
- // Update localStorage
81
- setUserInfo(state.user);
82
- },
83
- refreshUser: (state, action: PayloadAction<UserData>) => {
84
- const userData = action.payload;
85
- if (userData) {
86
- state.user = userData;
87
- state.isSignedIn = true;
88
- state.pending = false;
89
-
90
- // Update localStorage
91
- setUserInfo(userData);
92
- setAccessToken(userData.accessToken);
93
- setRefreshToken(userData.refreshToken);
94
- }
95
- },
96
- setPending: (state, action: PayloadAction<boolean>) => {
97
- state.pending = action.payload;
98
- },
99
- initializeFromStorage: (state) => {
100
- const storedUser = getUserInfo();
101
- const accessToken = getAccessToken();
102
-
103
- if (storedUser && accessToken) {
104
- state.user = storedUser;
105
- state.isSignedIn = true;
106
- }
107
- state.pending = false;
108
- },
109
- },
110
- });
111
-
112
- export const {
113
- signIn,
114
- signOut,
115
- updateScanStatus,
116
- refreshUser,
117
- setPending,
118
- initializeFromStorage,
119
- } = authSlice.actions;
120
-
121
- // Selectors
122
- export const selectAuth = (state: any) => state.auth;
123
- export const selectUser = (state: any) => state.auth.user;
124
- export const selectUserUId = (state: any) =>
125
- state.auth.user.uid || state.auth.user.id;
126
- export const selectUserAccesstoken = (state: any) =>
127
- state.auth.user.accessToken;
128
- export const selectUserEmail = (state: any) => state.auth.user.email;
129
- export const selectUsername = (state: any) => state.auth.user.userName;
130
- export const selectSignInProvider = (state: any) =>
131
- state.auth.user.signInProvider;
132
- export const selectUserScanStatus = (state: any) =>
133
- state.auth.user.scanStatus;
134
- export const selectIsSignedIn = (state: any) => state.auth.isSignedIn;
135
- export const selectIsLoading = (state: any) => state.auth.pending;
136
-
137
- export default authSlice.reducer;