@phygitallabs/tapquest-core 2.8.0 → 2.9.1

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 (36) hide show
  1. package/README.md +1 -1
  2. package/dist/index.d.ts +29 -35
  3. package/dist/index.js +1015 -721
  4. package/dist/index.js.map +1 -1
  5. package/package.json +6 -3
  6. package/src/modules/auth/constants/index.ts +6 -0
  7. package/src/modules/auth/helpers/index.ts +1 -4
  8. package/src/modules/auth/hooks/index.ts +2 -0
  9. package/src/modules/auth/hooks/useGoogleLogin.ts +169 -0
  10. package/src/modules/auth/hooks/useTokenRefresher.ts +39 -0
  11. package/src/modules/auth/index.ts +8 -2
  12. package/src/modules/auth/providers/AuthProvider.tsx +214 -186
  13. package/src/modules/auth/store/authStore.ts +577 -0
  14. package/src/modules/auth/types/auth.ts +29 -0
  15. package/src/modules/auth/types/user-data.ts +38 -0
  16. package/src/modules/auth/utils/user.ts +21 -0
  17. package/src/modules/data-tracking/hooks/index.ts +25 -1
  18. package/src/modules/generate-certificate/helpers/index.ts +3 -0
  19. package/src/modules/generate-certificate/hooks/index.ts +15 -6
  20. package/src/modules/generate-certificate/index.ts +3 -1
  21. package/src/modules/notification/providers/index.tsx +3 -3
  22. package/src/modules/reward/hooks/useRewardService.ts +6 -6
  23. package/src/modules/session-replay/README.md +334 -0
  24. package/src/modules/session-replay/hooks/useSessionReplay.ts +16 -0
  25. package/src/modules/session-replay/index.ts +10 -0
  26. package/src/modules/session-replay/providers/SessionReplayProvider.tsx +189 -0
  27. package/src/modules/session-replay/types/index.ts +147 -0
  28. package/src/modules/session-replay/utils/index.ts +12 -0
  29. package/src/providers/ServicesProvider.tsx +4 -76
  30. package/src/providers/TapquestCoreProvider.tsx +33 -46
  31. package/tsup.config.ts +1 -1
  32. package/dist/index.cjs +0 -1531
  33. package/dist/index.cjs.map +0 -1
  34. package/dist/index.d.cts +0 -690
  35. package/src/modules/auth/helpers/refreshToken.ts +0 -63
  36. package/src/modules/auth/store/authSlice.ts +0 -137
@@ -0,0 +1,3 @@
1
+ import { wrapTextWithFont } from "@phygitallabs/generate-certificate";
2
+
3
+ export { wrapTextWithFont };
@@ -1,8 +1,17 @@
1
- import {
2
- useGenerateThaocamvienCertificate,
3
- useGenerateTemplateCertificate,
4
- useGenerateFansipanCertificate
1
+ import {
2
+ useGenerateThaocamvienCertificate,
3
+ useGenerateTemplateCertificate,
4
+ useGenerateFansipanCertificate,
5
+ useCreateCertificate,
6
+ useCreateCertificateAnonymous,
7
+ useCreateCertificateWithMask,
5
8
  } from "@phygitallabs/generate-certificate";
6
9
 
7
-
8
- export { useGenerateThaocamvienCertificate, useGenerateTemplateCertificate, useGenerateFansipanCertificate };
10
+ export {
11
+ useGenerateThaocamvienCertificate,
12
+ useGenerateTemplateCertificate,
13
+ useGenerateFansipanCertificate,
14
+ useCreateCertificate,
15
+ useCreateCertificateAnonymous,
16
+ useCreateCertificateWithMask,
17
+ };
@@ -1,3 +1,5 @@
1
1
  export * from "./types";
2
2
 
3
- export * from "./hooks";
3
+ export * from "./hooks";
4
+
5
+ export * from "./helpers";
@@ -16,7 +16,7 @@ interface NotificationCallbacks {
16
16
  interface NotificationProviderProps extends NotificationCallbacks {
17
17
  children: React.ReactNode;
18
18
  autoConnect?: boolean;
19
- user: { id: string; accessToken: string | null };
19
+ user: { id?: string ; accessToken?: string };
20
20
  environment?: EnvironmentType;
21
21
  }
22
22
 
@@ -34,8 +34,8 @@ export const NotificationProvider: React.FC<NotificationProviderProps> = ({
34
34
 
35
35
  return (
36
36
  <NotificationProviderApi
37
- userUid={user.id}
38
- accessToken={user.accessToken}
37
+ userUid={user?.id}
38
+ accessToken={user?.accessToken ?? null}
39
39
  webSocketUrl={webSocketUrl}
40
40
  autoConnect={autoConnect}
41
41
  onWebSocketOpen={onWebSocketOpen}
@@ -3,12 +3,12 @@ export {
3
3
  useManyUserRewards,
4
4
  useGetUserRewards,
5
5
  useClaimUserReward,
6
- useApiListRewardModels as useListRewardModels,
7
- useApiGetRewardModel as useGetRewardModel,
8
- useApiCreateRewardModel as useCreateRewardModel,
9
- useApiUpdateRewardModel as useUpdateRewardModel,
10
- useApiDeleteRewardModel as useDeleteRewardModel,
6
+ useListRewardModels,
7
+ useGetRewardModel,
8
+ useCreateRewardModel,
9
+ useUpdateRewardModel,
10
+ useDeleteRewardModel,
11
11
  useCreateModelGroupReward,
12
12
  useClearUserRewardCache,
13
13
  useV1ListRewards
14
- } from "@phygitallabs/reward/src/hooks";
14
+ } from "@phygitallabs/reward";
@@ -0,0 +1,334 @@
1
+ # OpenReplay Session Replay Integration
2
+
3
+ This module provides a React context-based integration for OpenReplay session replay tracking, following the official Next.js tracker context pattern.
4
+
5
+ ## Features
6
+
7
+ - ✅ React Context API with reducer pattern
8
+ - ✅ Dynamic user ID management with `setUserId` action
9
+ - ✅ SSR-safe with dynamic imports
10
+ - ✅ TypeScript support
11
+ - ✅ Automatic or custom user ID generation
12
+ - ✅ Flexible configuration options
13
+
14
+ ## Installation
15
+
16
+ The module is already part of `@tapquest/core`. Make sure you have the required dependencies:
17
+
18
+ ```bash
19
+ pnpm add @openreplay/tracker uuid
20
+ pnpm add -D @types/uuid
21
+ ```
22
+
23
+ ## Basic Usage
24
+
25
+ ### 1. Wrap Your App with SessionReplayProvider
26
+
27
+ In your `_app.tsx` or root layout file:
28
+
29
+ ```tsx
30
+ import { SessionReplayProvider } from '@tapquest/core';
31
+
32
+ function MyApp({ Component, pageProps }) {
33
+ return (
34
+ <SessionReplayProvider
35
+ config={{
36
+ projectKey: process.env.NEXT_PUBLIC_OPENREPLAY_PROJECT_KEY,
37
+ userIdEnabled: false, // Set to true to enable automatic UUID generation
38
+ debug: process.env.NODE_ENV === 'development',
39
+ }}
40
+ >
41
+ <Component {...pageProps} />
42
+ </SessionReplayProvider>
43
+ );
44
+ }
45
+
46
+ export default MyApp;
47
+ ```
48
+
49
+ ### 2. Initialize Tracker in a Component
50
+
51
+ #### Option A: Using the `useSessionReplay` hook (Recommended)
52
+
53
+ ```tsx
54
+ import { useEffect } from 'react';
55
+ import { useSessionReplay } from '@tapquest/core';
56
+
57
+ function MyComponent() {
58
+ const { initTracker, startTracking } = useSessionReplay();
59
+
60
+ useEffect(() => {
61
+ initTracker();
62
+ startTracking();
63
+ }, [initTracker, startTracking]);
64
+
65
+ return <div>Your content</div>;
66
+ }
67
+ ```
68
+
69
+ #### Option B: Using `TrackerContext` directly
70
+
71
+ ```tsx
72
+ import { useContext, useEffect } from 'react';
73
+ import { TrackerContext } from '@tapquest/core';
74
+
75
+ function MyComponent() {
76
+ const tracker = useContext(TrackerContext);
77
+
78
+ useEffect(() => {
79
+ if (tracker) {
80
+ tracker.initTracker();
81
+ tracker.startTracking();
82
+ }
83
+ }, [tracker]);
84
+
85
+ return <div>Your content</div>;
86
+ }
87
+ ```
88
+
89
+ ## Advanced Usage
90
+
91
+ ### Custom User ID Function
92
+
93
+ Provide a custom function to generate user IDs based on your authentication system:
94
+
95
+ ```tsx
96
+ import { SessionReplayProvider } from '@tapquest/core';
97
+ import { useAuthStore } from './stores/authStore';
98
+
99
+ function MyApp({ Component, pageProps }) {
100
+ return (
101
+ <SessionReplayProvider
102
+ config={{
103
+ projectKey: process.env.NEXT_PUBLIC_OPENREPLAY_PROJECT_KEY,
104
+ userIdEnabled: true,
105
+ getUserId: () => {
106
+ // Return the current user's ID from your auth system
107
+ const user = useAuthStore.getState().user;
108
+ return user?.id || user?.email || 'anonymous';
109
+ },
110
+ debug: process.env.NODE_ENV === 'development',
111
+ }}
112
+ >
113
+ <Component {...pageProps} />
114
+ </SessionReplayProvider>
115
+ );
116
+ }
117
+ ```
118
+
119
+ ### Updating User ID After Login
120
+
121
+ Use the `setUserId` action to update the user ID when a user logs in:
122
+
123
+ ```tsx
124
+ import { useSessionReplay } from '@tapquest/core';
125
+
126
+ function LoginComponent() {
127
+ const { setUserId } = useSessionReplay();
128
+
129
+ const handleLogin = async (credentials) => {
130
+ const user = await loginUser(credentials);
131
+
132
+ // Update the tracker with the authenticated user's ID
133
+ setUserId(user.id);
134
+ };
135
+
136
+ return <LoginForm onSubmit={handleLogin} />;
137
+ }
138
+ ```
139
+
140
+ ### Self-Hosted OpenReplay
141
+
142
+ For self-hosted OpenReplay instances, specify the ingest point:
143
+
144
+ ```tsx
145
+ <SessionReplayProvider
146
+ config={{
147
+ projectKey: 'your-project-key',
148
+ ingestPoint: 'https://openreplay.yourdomain.com/ingest',
149
+ userIdEnabled: true,
150
+ }}
151
+ >
152
+ {children}
153
+ </SessionReplayProvider>
154
+ ```
155
+
156
+ ### Full Configuration Example
157
+
158
+ ```tsx
159
+ import { SessionReplayProvider } from '@tapquest/core';
160
+
161
+ function MyApp({ Component, pageProps }) {
162
+ return (
163
+ <SessionReplayProvider
164
+ config={{
165
+ // Required
166
+ projectKey: process.env.NEXT_PUBLIC_OPENREPLAY_PROJECT_KEY,
167
+
168
+ // User identification
169
+ userIdEnabled: true,
170
+ getUserId: () => getCurrentUser()?.id || 'anonymous',
171
+
172
+ // Custom ingest point (for self-hosted)
173
+ ingestPoint: 'https://openreplay.yourdomain.com/ingest',
174
+
175
+ // Capture options
176
+ captureExceptions: true,
177
+ capturePerformance: true,
178
+
179
+ // Network tracking
180
+ network: {
181
+ capturePayload: true,
182
+ sanitizer: (data) => {
183
+ // Redact sensitive information
184
+ if (data.request?.headers?.Authorization) {
185
+ data.request.headers.Authorization = '[REDACTED]';
186
+ }
187
+ return data;
188
+ },
189
+ },
190
+
191
+ // Console tracking
192
+ console: {
193
+ levels: ['error', 'warn', 'log'],
194
+ },
195
+
196
+ // Privacy settings
197
+ obscureTextEmails: true,
198
+ obscureTextNumbers: false,
199
+ obscureInputEmails: true,
200
+
201
+ // Development mode
202
+ debug: process.env.NODE_ENV === 'development',
203
+ __DISABLE_SECURE_MODE: process.env.NODE_ENV === 'development',
204
+ }}
205
+ >
206
+ <Component {...pageProps} />
207
+ </SessionReplayProvider>
208
+ );
209
+ }
210
+ ```
211
+
212
+ ## API Reference
213
+
214
+ ### SessionReplayProvider Props
215
+
216
+ ```typescript
217
+ interface SessionReplayProviderProps {
218
+ children: React.ReactNode;
219
+ config?: OpenReplayConfig;
220
+ }
221
+ ```
222
+
223
+ ### OpenReplayConfig
224
+
225
+ ```typescript
226
+ interface OpenReplayConfig {
227
+ projectKey?: string;
228
+ ingestPoint?: string;
229
+ userIdEnabled?: boolean;
230
+ getUserId?: () => string;
231
+ debug?: boolean;
232
+ captureExceptions?: boolean;
233
+ capturePerformance?: boolean;
234
+ network?: {
235
+ capturePayload?: boolean;
236
+ sanitizer?: (data: any) => any;
237
+ };
238
+ console?: {
239
+ levels?: Array<"error" | "warn" | "log" | "info" | "debug">;
240
+ };
241
+ obscureTextEmails?: boolean;
242
+ obscureTextNumbers?: boolean;
243
+ obscureInputEmails?: boolean;
244
+ __DISABLE_SECURE_MODE?: boolean;
245
+ }
246
+ ```
247
+
248
+ ### TrackerContextValue
249
+
250
+ ```typescript
251
+ interface TrackerContextValue {
252
+ initTracker: () => void;
253
+ startTracking: () => void;
254
+ setUserId: (userId: string) => void;
255
+ }
256
+ ```
257
+
258
+ ## Environment Variables
259
+
260
+ Store your OpenReplay project key in an environment variable:
261
+
262
+ ```bash
263
+ # .env.local
264
+ NEXT_PUBLIC_OPENREPLAY_PROJECT_KEY=your_project_key_here
265
+ ```
266
+
267
+ **Note:** Use the `NEXT_PUBLIC_` prefix to make the variable accessible in the browser.
268
+
269
+ ## Best Practices
270
+
271
+ 1. **Initialize Once**: Call `initTracker()` and `startTracking()` once when your app loads, typically in a root-level component or effect.
272
+
273
+ 2. **Update User ID After Auth**: Use `setUserId()` after successful login to associate sessions with authenticated users.
274
+
275
+ 3. **Sanitize Sensitive Data**: Use the `network.sanitizer` function to redact sensitive information like API keys, passwords, and PII.
276
+
277
+ 4. **Use Environment Variables**: Never hardcode your project key. Use environment variables with the `NEXT_PUBLIC_` prefix.
278
+
279
+ 5. **Enable Debug Mode in Development**: Set `debug: true` in development to see tracker logs in the console.
280
+
281
+ ## Migration from Previous Implementation
282
+
283
+ If you're migrating from the old `useEffect`-based implementation:
284
+
285
+ **Before:**
286
+ ```tsx
287
+ // Provider auto-initialized and started tracking
288
+ <SessionReplayProvider config={{ projectKey, userId: user.id }}>
289
+ {children}
290
+ </SessionReplayProvider>
291
+ ```
292
+
293
+ **After:**
294
+ ```tsx
295
+ // Provider requires explicit initialization
296
+ <SessionReplayProvider config={{ projectKey, userIdEnabled: true }}>
297
+ {children}
298
+ </SessionReplayProvider>
299
+
300
+ // In a component:
301
+ const tracker = useContext(TrackerContext);
302
+ useEffect(() => {
303
+ tracker?.initTracker();
304
+ tracker?.startTracking();
305
+ }, []);
306
+
307
+ // Update user ID dynamically:
308
+ tracker?.setUserId(user.id);
309
+ ```
310
+
311
+ ## Troubleshooting
312
+
313
+ ### Tracker not initializing
314
+
315
+ - Ensure you're calling `initTracker()` before `startTracking()`
316
+ - Check that your project key is set correctly
317
+ - Verify you're in a browser environment (not SSR)
318
+
319
+ ### User ID not updating
320
+
321
+ - Make sure `userIdEnabled` is set to `true`
322
+ - Call `setUserId()` after the tracker is initialized
323
+ - Check debug logs to verify the user ID was set
324
+
325
+ ### TypeScript errors
326
+
327
+ - Ensure you're importing types from `@tapquest/core`
328
+ - Verify that `TrackerContext` returns a non-null value before using it
329
+
330
+ ## Links
331
+
332
+ - [OpenReplay Documentation](https://docs.openreplay.com/)
333
+ - [OpenReplay Next.js Integration](https://docs.openreplay.com/en/using-or/next/)
334
+ - [OpenReplay Tracker API](https://docs.openreplay.com/en/using-or/tracker-api/)
@@ -0,0 +1,16 @@
1
+ import { useContext } from "react";
2
+ import { TrackerContext } from "../providers/SessionReplayProvider";
3
+ import { TrackerContextValue } from "../types";
4
+
5
+ export function useSessionReplay(): TrackerContextValue {
6
+ const context = useContext(TrackerContext);
7
+
8
+ if (!context) {
9
+ throw new Error(
10
+ "useSessionReplay must be used within a SessionReplayProvider. " +
11
+ "Make sure your component is wrapped with <SessionReplayProvider>."
12
+ );
13
+ }
14
+
15
+ return context;
16
+ }
@@ -0,0 +1,10 @@
1
+ export { SessionReplayProvider, TrackerContext } from "./providers/SessionReplayProvider";
2
+ export { useSessionReplay } from "./hooks/useSessionReplay";
3
+ export type {
4
+ OpenReplayConfig,
5
+ SessionReplayProviderProps,
6
+ TrackerContextValue,
7
+ TrackerState,
8
+ TrackerAction,
9
+ } from "./types";
10
+ export * from "./utils";
@@ -0,0 +1,189 @@
1
+ import React, { createContext, useEffect, useReducer } from "react";
2
+ import { v4 as uuidV4 } from "uuid";
3
+
4
+ import {
5
+ SessionReplayProviderProps,
6
+ TrackerState,
7
+ TrackerAction,
8
+ TrackerContextValue,
9
+ OpenReplayConfig,
10
+ } from "../types";
11
+
12
+ import {
13
+ isBrowser
14
+ } from "../utils";
15
+
16
+ export const TrackerContext = createContext<TrackerContextValue | null>(null);
17
+
18
+ function defaultGetUserId(): string {
19
+ return uuidV4();
20
+ }
21
+
22
+ async function newTracker(config: OpenReplayConfig) {
23
+ try {
24
+ // Dynamic import for SSR compatibility
25
+ const OpenReplay = (await import("@openreplay/tracker")).default;
26
+
27
+ // Get user ID function (custom or default UUID generator)
28
+ const getUserId =
29
+ config?.userIdEnabled && config?.getUserId
30
+ ? config.getUserId
31
+ : defaultGetUserId;
32
+
33
+ // Build tracker configuration
34
+ const trackerConfig: any = {
35
+ projectKey:
36
+ config?.projectKey || process.env.NEXT_PUBLIC_OPENREPLAY_PROJECT_KEY,
37
+ ingestPoint: config?.ingestPoint,
38
+ // Capture options
39
+ captureExceptions: config.captureExceptions ?? true,
40
+ capturePerformance: config.capturePerformance ?? true,
41
+ // Network tracking
42
+ network: config.network || {
43
+ capturePayload: true,
44
+ sanitizer: (data: any) => data,
45
+ },
46
+ // Console tracking
47
+ console: config.console || {
48
+ levels: ["error", "warn", "log"],
49
+ },
50
+ // Privacy settings
51
+ obscureTextEmails: config.obscureTextEmails ?? true,
52
+ obscureTextNumbers: config.obscureTextNumbers ?? false,
53
+ obscureInputEmails: config.obscureInputEmails ?? true,
54
+ // Development mode
55
+ __DISABLE_SECURE_MODE:
56
+ config.__DISABLE_SECURE_MODE ??
57
+ (typeof process !== "undefined" && process.env?.NODE_ENV === "development"),
58
+ };
59
+
60
+ // Initialize tracker
61
+ const tracker = new OpenReplay(trackerConfig);
62
+
63
+ // Set user ID if enabled
64
+ if (config?.userIdEnabled) {
65
+ const userId = getUserId();
66
+ tracker.setUserID(userId);
67
+ console.log("User ID set:", userId);
68
+ }
69
+
70
+ console.log("OpenReplay tracker initialized");
71
+ console.log("Project Key:", trackerConfig.projectKey);
72
+ console.log("Ingest Point:", trackerConfig.ingestPoint);
73
+
74
+ return tracker;
75
+ } catch (error: any) {
76
+ console.error("Failed to create tracker:", error);
77
+ throw error;
78
+ }
79
+ }
80
+
81
+ function reducer(state: TrackerState, action: TrackerAction): TrackerState {
82
+ const { debug = false } = state.config;
83
+
84
+ switch (action.type) {
85
+ case "init":
86
+ // Only initialize if tracker doesn't exist and we're in browser
87
+ if (!state.tracker && isBrowser()) {
88
+ if (!state.config.projectKey && !process.env.NEXT_PUBLIC_OPENREPLAY_PROJECT_KEY) {
89
+ console.warn(
90
+ debug,
91
+ "Project key not found. Skipping session replay initialization."
92
+ );
93
+ return state;
94
+ }
95
+
96
+ // Return state with tracker promise
97
+ // The tracker will be created asynchronously
98
+ return {
99
+ ...state,
100
+ tracker: newTracker(state.config),
101
+ };
102
+ }
103
+ return state;
104
+
105
+ case "start":
106
+ // Start tracking if tracker exists
107
+ if (state.tracker) {
108
+ Promise.resolve(state.tracker)
109
+ .then((tracker) => {
110
+ tracker.start();
111
+ console.log(debug, "Session replay tracker started");
112
+ })
113
+ .catch((error) => {
114
+ console.error("Failed to start tracker:", error);
115
+ });
116
+ } else {
117
+ console.warn(debug, "Tracker not initialized. Call initTracker() first.");
118
+ }
119
+ return state;
120
+
121
+ case "setUserId":
122
+ // Set or update user ID
123
+ if (state.tracker) {
124
+ Promise.resolve(state.tracker)
125
+ .then((tracker) => {
126
+ tracker.setUserID(action.payload);
127
+ console.log(debug, "User ID updated:", action.payload);
128
+ })
129
+ .catch((error) => {
130
+ console.error("Failed to set user ID:", error);
131
+ });
132
+ } else {
133
+ console.warn(debug, "Tracker not initialized. Call initTracker() first.");
134
+ }
135
+ return state;
136
+
137
+ // Set metadata
138
+ case "setMetadata":
139
+ if (state.tracker) {
140
+ Promise.resolve(state.tracker)
141
+ .then((tracker) => {
142
+ Object.entries(action.payload || {})?.forEach(([key, value]) => {
143
+ tracker.setMetadata(key, value);
144
+ });
145
+
146
+ console.log(debug, "Metadata updated:", action.payload.metadata);
147
+ })
148
+ .catch((error) => {
149
+ console.error("Failed to set metadata:", error);
150
+ });
151
+ } else {
152
+ console.warn(debug, "Tracker not initialized. Call initTracker() first.");
153
+ }
154
+ return state;
155
+
156
+ default:
157
+ return state;
158
+ }
159
+ }
160
+
161
+ export const SessionReplayProvider: React.FC<SessionReplayProviderProps> = ({
162
+ children,
163
+ config = {},
164
+ }) => {
165
+ const [, dispatch] = useReducer(reducer, {
166
+ tracker: null,
167
+ config,
168
+ });
169
+
170
+ const initTracker = () => dispatch({ type: "init" })
171
+ const startTracking = () => dispatch({ type: "start" })
172
+ const setUserId = (userId: string) => dispatch({ type: "setUserId", payload: userId })
173
+ const setMetadata = (metadata: Record<string, any>) => dispatch({ type: "setMetadata", payload: metadata })
174
+
175
+ // init and start tracker
176
+ useEffect(() => {
177
+ initTracker();
178
+ startTracking();
179
+ }, []);
180
+
181
+ return <TrackerContext.Provider value={{
182
+ initTracker,
183
+ startTracking,
184
+ setUserId,
185
+ setMetadata
186
+ }}>{children}</TrackerContext.Provider>;
187
+ };
188
+
189
+ export default SessionReplayProvider;