-
Notifications
You must be signed in to change notification settings - Fork 0
feat(sdk) : added framework support and sdk support #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,7 +1,17 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Worker, Queue } from "bullmq"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { redis } from "@glimpse/db/redis"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { prisma } from "@glimpse/db/client"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * What to precompute for analytics: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * AnalyticsSession | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * DailyEventAggregate | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * DailyPageAnalytics | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * DailyUserAnalytics | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * AnalyticsCheckpoint | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const redisPort = process.env.REDIS_PORT | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ? Number(process.env.REDIS_PORT) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| : 6379; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -23,7 +33,18 @@ const analyticsWorker = new Worker( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| switch (job.name) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case "analytics-job": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { data } = job; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Process the analytics data | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // AnalyticsSession computation | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const analyticsSession = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| projectId: data.projectId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sessionId: data.sessionId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| createdAt : new Date(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updatedAt : new Date(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
34
to
48
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Incomplete implementation leaves
Suggested change
Prompt To Fix With AIThis is a comment left during a code review.
Path: app/analytics-worker/analyticsWorker.ts
Line: 34:48
Comment:
Incomplete implementation leaves `analyticsSession` object constructed but never used - no database insert or return statement. The `break` statement exits the switch without processing.
```suggestion
case "analytics-job":
const { data } = job;
// AnalyticsSession computation
const analyticsSession = {
projectId: data.projectId,
sessionId: data.sessionId,
createdAt: new Date(),
updatedAt: new Date(),
};
// Store in database
await prisma.analyticsSession.upsert({
where: { sessionId: analyticsSession.sessionId },
update: { updatedAt: analyticsSession.updatedAt },
create: analyticsSession,
});
break;
```
How can I resolve this? If you propose a fix, please make it concise. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| default: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.warn(`Unknown job type: ${job.name}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| /** | ||
| * SDK Configuration | ||
| * Supports both script tag attributes and programmatic initialization | ||
| */ | ||
|
|
||
| let config = { | ||
| projectId: undefined, | ||
| endpoint: 'http://localhost:3000/event', | ||
| autoTrack: true, | ||
| debug: false, | ||
| trackPageViews: true, | ||
| trackWebVitals: true, | ||
| trackEngagement: true, | ||
| trackErrors: true, | ||
| sessionTimeout: 30 * 60 * 1000, // 30 minutes | ||
| }; | ||
|
|
||
| let initialized = false; | ||
|
|
||
| /** | ||
| * Check if running in browser | ||
| */ | ||
| export function isBrowser() { | ||
| return typeof window !== 'undefined' && typeof document !== 'undefined'; | ||
| } | ||
|
|
||
| /** | ||
| * Get current config | ||
| */ | ||
| export function getConfig() { | ||
| return { ...config }; | ||
| } | ||
|
|
||
| /** | ||
| * Initialize the SDK programmatically | ||
| * Use this for React, Vue, Next.js, etc. | ||
| */ | ||
| export function init(options = {}) { | ||
| if (!isBrowser()) { | ||
| console.warn('GlimpseTracker: Cannot initialize on server. Use in useEffect/onMounted.'); | ||
| return false; | ||
| } | ||
|
|
||
| if (initialized && !options.force) { | ||
| if (config.debug) console.info('GlimpseTracker: Already initialized'); | ||
| return true; | ||
| } | ||
|
|
||
| config = { | ||
| ...config, | ||
| ...options, | ||
| }; | ||
|
|
||
| if (!config.projectId) { | ||
| console.warn('GlimpseTracker: projectId is required'); | ||
| return false; | ||
| } | ||
|
|
||
| initialized = true; | ||
|
|
||
| // Update global tracker | ||
| if (window.GlimpseTracker) { | ||
| window.GlimpseTracker.projectId = config.projectId; | ||
| window.GlimpseTracker.endpoint = config.endpoint; | ||
| window.GlimpseTracker.debug = config.debug; | ||
| } | ||
|
|
||
| if (config.debug) { | ||
| console.info('GlimpseTracker: Initialized', config); | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| /** | ||
| * Check if SDK is initialized | ||
| */ | ||
| export function isInitialized() { | ||
| return initialized; | ||
| } | ||
|
|
||
| /** | ||
| * Reset initialization (for testing) | ||
| */ | ||
| export function resetInit() { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Prompt for AI agents |
||
| initialized = false; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,43 +1,78 @@ | ||
| import { getAnonymousId, getUserId, getTraits } from './identity.js'; | ||
|
|
||
| // Track previous path within the session | ||
| let prevPath = sessionStorage.getItem('analytics_prev_path') || undefined; | ||
| let prevPath = sessionStorage.getItem('glimpse_prev_path') || undefined; | ||
|
|
||
| export function createEvent(projectId, sessionId, name, properties) { | ||
| const currentPath = location.pathname + location.search + location.hash; | ||
| /** | ||
| * Create a fully-formed event object matching the schema | ||
| */ | ||
| export function createEvent(projectId, sessionId, name, properties = {}) { | ||
| const currentPath = location.pathname + location.search; | ||
|
|
||
| const evt = { | ||
| projectId, | ||
| event: name, | ||
| timestamp: Date.now(), | ||
|
|
||
| // Identity | ||
| sessionId, | ||
| anonymousId: getAnonymousId(), | ||
| userId: getUserId(), | ||
| traits: getTraits(), | ||
|
|
||
| // Event data | ||
| properties: properties || {}, | ||
|
|
||
| // Context | ||
| context: { | ||
| // Page info | ||
| url: window.location.href, | ||
| referrer: document.referrer || undefined, | ||
| path: currentPath, | ||
| hash: location.hash || undefined, | ||
| title: document.title || undefined, | ||
| referrer: document.referrer || undefined, | ||
| previousPath: prevPath, | ||
|
|
||
| viewport: `${window.innerWidth}x${window.innerHeight}`, | ||
|
|
||
| // Device & viewport | ||
| viewport: { | ||
| width: window.innerWidth, | ||
| height: window.innerHeight | ||
| }, | ||
| screen: { | ||
| width: window.screen.width, | ||
| height: window.screen.height, | ||
| colorDepth: window.screen.colorDepth | ||
| colorDepth: window.screen.colorDepth, | ||
| pixelRatio: window.devicePixelRatio || 1 | ||
| }, | ||
|
|
||
|
|
||
| // Browser info | ||
| userAgent: navigator.userAgent, | ||
| language: navigator.language, | ||
| languages: navigator.languages ? [...navigator.languages] : [navigator.language], | ||
| timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, | ||
|
|
||
| connection: navigator.connection && { | ||
| cookiesEnabled: navigator.cookieEnabled, | ||
|
|
||
| // Connection info | ||
| connection: navigator.connection ? { | ||
| effectiveType: navigator.connection.effectiveType, | ||
| saveData: navigator.connection.saveData, | ||
| }, | ||
| downlink: navigator.connection.downlink, | ||
| rtt: navigator.connection.rtt, | ||
| saveData: navigator.connection.saveData | ||
| } : undefined, | ||
|
|
||
| // Touch capability | ||
| touchPoints: navigator.maxTouchPoints || 0, | ||
|
|
||
| // Platform hints | ||
| platform: navigator.userAgentData ? { | ||
| mobile: navigator.userAgentData.mobile, | ||
| platform: navigator.userAgentData.platform | ||
| } : undefined | ||
|
Comment on lines
+67
to
+70
|
||
| } | ||
| }; | ||
|
|
||
| prevPath = currentPath; | ||
| sessionStorage.setItem('analytics_prev_path', currentPath); | ||
| sessionStorage.setItem('glimpse_prev_path', currentPath); | ||
|
|
||
| return evt; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,69 @@ | ||||||
| const ANON_ID_KEY = 'glimpse_anonymous_id'; | ||||||
| const USER_ID_KEY = 'glimpse_user_id'; | ||||||
| const TRAITS_KEY = 'glimpse_traits'; | ||||||
|
|
||||||
| /** | ||||||
| * Get or create a persistent anonymous ID | ||||||
| */ | ||||||
| export function getAnonymousId() { | ||||||
| let anonId = localStorage.getItem(ANON_ID_KEY); | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Missing error handling for localStorage access. This function accesses Prompt for AI agents |
||||||
| if (!anonId) { | ||||||
| anonId = `anon_${crypto.randomUUID ? crypto.randomUUID() : generateUUID()}`; | ||||||
|
||||||
| anonId = `anon_${crypto.randomUUID ? crypto.randomUUID() : generateUUID()}`; | |
| anonId = `anon_${typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function' ? crypto.randomUUID() : generateUUID()}`; |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,9 +1,72 @@ | ||||||
| const SESSION_ID_KEY = 'glimpse_session_id'; | ||||||
| const SESSION_START_KEY = 'glimpse_session_start'; | ||||||
| const SESSION_LAST_ACTIVE_KEY = 'glimpse_session_last_active'; | ||||||
| const SESSION_TIMEOUT = 30 * 60 * 1000; // 30 minutes | ||||||
|
|
||||||
| /** | ||||||
| * Get or create session ID | ||||||
| * Sessions expire after 30 minutes of inactivity | ||||||
| */ | ||||||
| export function getSessionId() { | ||||||
| let sid = sessionStorage.getItem('analytics_session_id'); | ||||||
| const now = Date.now(); | ||||||
| let sid = sessionStorage.getItem(SESSION_ID_KEY); | ||||||
| const lastActive = parseInt(sessionStorage.getItem(SESSION_LAST_ACTIVE_KEY) || '0', 10); | ||||||
|
|
||||||
| // Check if session expired | ||||||
| if (sid && lastActive && (now - lastActive > SESSION_TIMEOUT)) { | ||||||
| // Session expired, create new one | ||||||
| sid = null; | ||||||
| } | ||||||
|
|
||||||
| if (!sid) { | ||||||
| sid = `sess_${Math.random().toString(36).substring(2, 15)}`; | ||||||
| sessionStorage.setItem('analytics_session_id', sid); | ||||||
| sid = `sess_${crypto.randomUUID ? crypto.randomUUID().slice(0, 12) : Math.random().toString(36).substring(2, 15)}`; | ||||||
|
||||||
| sid = `sess_${crypto.randomUUID ? crypto.randomUUID().slice(0, 12) : Math.random().toString(36).substring(2, 15)}`; | |
| sid = `sess_${crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).substring(2, 15)}`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isNewSession() has incorrect logic - it returns true only before getSessionId() is first called, but if called after, it always returns false even for genuinely new sessions (e.g., after 30min timeout). This breaks session start tracking.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/analytics-web/core/session.js
Line: 36:38
Comment:
`isNewSession()` has incorrect logic - it returns true only before `getSessionId()` is first called, but if called after, it always returns false even for genuinely new sessions (e.g., after 30min timeout). This breaks session start tracking.
How can I resolve this? If you propose a fix, please make it concise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The analyticsSession object is created but never used. This appears to be incomplete placeholder code with empty lines (42-43). Either complete the implementation or remove this unused code.