-
Notifications
You must be signed in to change notification settings - Fork 0
Unify experiment codebase: fix PID generation, timing accuracy, Firebase sync, and XSS vulnerabilities #20
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 | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -14,18 +14,37 @@ const CFG = Object.freeze({ | |||||||||||||||
| COLLECTION: 'conformity_telemetry' | ||||||||||||||||
| }); | ||||||||||||||||
|
|
||||||||||||||||
| // --- Participant ID Generation --- | ||||||||||||||||
| function generatePID() { | ||||||||||||||||
| try { | ||||||||||||||||
| return self.crypto.randomUUID(); | ||||||||||||||||
| } catch (e) { | ||||||||||||||||
| return Math.random().toString(36).substring(2, 15) + | ||||||||||||||||
| Math.random().toString(36).substring(2, 15); | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| // --- State Machine --- | ||||||||||||||||
| const STATE = { | ||||||||||||||||
| pid: Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15), | ||||||||||||||||
| pid: localStorage.getItem('experiment_pid') || generatePID(), | ||||||||||||||||
| condition: CFG.CONDITION, | ||||||||||||||||
| covariate: 0, | ||||||||||||||||
| currentTrial: 0, | ||||||||||||||||
| results: [], // Tidy Data Long Format | ||||||||||||||||
| results: [], | ||||||||||||||||
| trialStartTime: 0, | ||||||||||||||||
| isTrialActive: false, | ||||||||||||||||
| justification: "" | ||||||||||||||||
| justification: "", | ||||||||||||||||
| activeScreen: null, | ||||||||||||||||
| pendingTransitionTimer: null | ||||||||||||||||
| }; | ||||||||||||||||
|
|
||||||||||||||||
| // Persist PID to localStorage | ||||||||||||||||
| try { | ||||||||||||||||
| localStorage.setItem('experiment_pid', STATE.pid); | ||||||||||||||||
| } catch (e) { | ||||||||||||||||
| // Silent fallback | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| // --- Trial Definitions (Pixel-Perfect Components) --- | ||||||||||||||||
| // --- High-Fidelity, Zero-Latency UI Trials (Iqra University Context) --- | ||||||||||||||||
| const TRIALS = [ | ||||||||||||||||
|
|
@@ -76,7 +95,7 @@ const TRIALS = [ | |||||||||||||||
| </div> | ||||||||||||||||
| </div> | ||||||||||||||||
| </div>`, | ||||||||||||||||
| target: 'B' // Hypothesized preference for modern Bento grid layouts | ||||||||||||||||
| target: 'B' | ||||||||||||||||
| }, | ||||||||||||||||
| { | ||||||||||||||||
| domain: 'Data Visualization (HEC Attendance Warning)', | ||||||||||||||||
|
|
@@ -122,7 +141,7 @@ const TRIALS = [ | |||||||||||||||
| <strong>Alert:</strong> You can only miss 2 more classes in RM-2 before facing HEC examination block. | ||||||||||||||||
| </div> | ||||||||||||||||
| </div>`, | ||||||||||||||||
| target: 'A' | ||||||||||||||||
| target: 'A' | ||||||||||||||||
| }, | ||||||||||||||||
| { | ||||||||||||||||
| domain: 'Financial Overview (Fee Voucher)', | ||||||||||||||||
|
|
@@ -156,7 +175,7 @@ const TRIALS = [ | |||||||||||||||
| </div> | ||||||||||||||||
| <h3 style="font-size:1.2rem; margin:0 0 5px 0;">Spring 2026 Invoice</h3> | ||||||||||||||||
| <p style="font-size:0.85rem; color:var(--text-secondary); margin:0 0 25px 0;">Challan: IU-9938-26</p> | ||||||||||||||||
|
|
||||||||||||||||
| <div style="background:var(--bg-surface); padding:20px; border-radius:16px; margin-bottom:20px;"> | ||||||||||||||||
| <span style="display:block; font-size:0.85rem; color:#ff453a; font-weight:600; text-transform:uppercase; letter-spacing:1px; margin-bottom:8px;">Due March 10</span> | ||||||||||||||||
| <span style="display:block; font-size:2rem; font-weight:800; letter-spacing:-1px;">Rs. 95,500</span> | ||||||||||||||||
|
|
@@ -242,7 +261,7 @@ const TRIALS = [ | |||||||||||||||
| <h3 style="font-size:1.1rem; margin:0 0 6px 0;">RM-2 Instructor Evaluation</h3> | ||||||||||||||||
| <p style="font-size:0.85rem; color:var(--text-secondary); margin:0; line-height:1.4;">"The instructor provided clear grading rubrics and feedback."</p> | ||||||||||||||||
| </div> | ||||||||||||||||
|
|
||||||||||||||||
| <div style="padding: 20px 10px;"> | ||||||||||||||||
| <div style="position:relative; width:100%; height:6px; background:var(--bg-surface); border-radius:3px;"> | ||||||||||||||||
| <div style="position:absolute; top:0; left:0; height:100%; width:75%; background:var(--accent-blue); border-radius:3px;"></div> | ||||||||||||||||
|
|
@@ -293,7 +312,6 @@ const TRIALS = [ | |||||||||||||||
|
|
||||||||||||||||
| // --- DOM Elements --- | ||||||||||||||||
| const DOM = { | ||||||||||||||||
| screens: document.querySelectorAll('.screen'), | ||||||||||||||||
| btnConsent: document.getElementById('btn-consent'), | ||||||||||||||||
| btnsFamiliarity: document.querySelectorAll('.btn-familiarity'), | ||||||||||||||||
| trialGrid: document.getElementById('trial-grid'), | ||||||||||||||||
|
|
@@ -308,34 +326,49 @@ const DOM = { | |||||||||||||||
|
|
||||||||||||||||
| // --- Navigation Logic --- | ||||||||||||||||
| function showScreen(id) { | ||||||||||||||||
| DOM.screens.forEach(s => { | ||||||||||||||||
| s.classList.remove('active'); | ||||||||||||||||
| s.style.display = 'none'; | ||||||||||||||||
| }); | ||||||||||||||||
| const target = document.getElementById(`screen-${id}`); | ||||||||||||||||
| const target = document.getElementById('screen-' + id); | ||||||||||||||||
| if (!target || target === STATE.activeScreen) return; | ||||||||||||||||
|
|
||||||||||||||||
| const outgoing = STATE.activeScreen; | ||||||||||||||||
| STATE.activeScreen = target; | ||||||||||||||||
|
|
||||||||||||||||
| if (outgoing) { | ||||||||||||||||
|
||||||||||||||||
| if (outgoing) { | |
| if (outgoing) { | |
| // Ensure outgoing screen stays visible during fade-out | |
| outgoing.style.display = 'flex'; |
Copilot
AI
Mar 12, 2026
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.
showScreen() schedules setTimeout(() => outgoing.style.display = 'none', 400) but doesn't cancel/guard previously scheduled hide timers. If the user navigates A→B and quickly back to A within 400ms, the earlier timer can still fire and hide the now-active screen. Track and clear a separate outgoing-hide timer (or check outgoing !== STATE.activeScreen inside the callback) to prevent stale timers from hiding the active view.
| setTimeout(() => { outgoing.style.display = 'none'; }, 400); | |
| setTimeout(() => { | |
| // Only hide if this screen is not the current active screen | |
| if (outgoing !== STATE.activeScreen) { | |
| outgoing.style.display = 'none'; | |
| } | |
| }, 400); |
Copilot
AI
Mar 12, 2026
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.
await db.waitForPendingWrites() can wait indefinitely when the client is offline (the preceding batch.commit() can still resolve when persistence is enabled). This can leave participants stuck on the “Synchronizing Payload…” screen with no PID shown and no local-fallback message. Consider adding an explicit timeout around waitForPendingWrites() and, on timeout, keep the local backup and surface the same local-save message + final-actions so the UI can always complete.
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.
STATE.pidis initialized withlocalStorage.getItem('experiment_pid')at module load. In some environments (e.g., storage blocked/disabled),localStorageaccess can throw aSecurityErrorand prevent the entire script from running. Wrap the read in a try/catch (or use a small helper) and fall back togeneratePID()when storage is unavailable.