UNPKG

jspsych-datamanager

Version:

A package to manage data for jsPsych experiments. Currently Firebase and Supabase are supported.

114 lines (113 loc) 3.71 kB
// src/firebase.ts import { initializeApp } from "firebase/app"; import { getFirestore, collection, doc, setDoc, updateDoc, arrayUnion } from "firebase/firestore"; import { DataManager } from "@jspsych-datamanager/core"; var FirebaseManager = class extends DataManager { /** * Creates a new FirebaseManager instance * @param firebaseConfig Firebase configuration object * @param options Additional options for initialization */ constructor(firebaseConfig, options = {}) { super(options.metadata); this.numberOfWrites = 0; this.app = initializeApp(firebaseConfig); this.db = getFirestore(this.app); const collectionName = options.collectionName || "experiments"; const documentId = options.documentId || void 0; this.docRef = documentId ? doc(this.db, collectionName, documentId) : doc(collection(this.db, collectionName)); } /** * Initializes the experiment document in Firestore * @param additionalData Additional data to include in the experiment document * @throws {Error} If initialization fails */ async initializeExperiment(additionalData = {}) { const initialData = { ...this.metadata, trials: [], ...additionalData }; try { await setDoc(this.docRef, initialData); this.numberOfWrites++; console.log("[FirebaseManager] Document successfully created!"); } catch (error) { console.error("[FirebaseManager] Error creating document:", error); throw new Error("Failed to initialize experiment document"); } } /** * Adds a new trial to the experiment document * @param trialData The trial data to add * @throws {Error} If storing the trial fails */ async addTrialData(trialData) { const flattenedData = this.flattenNestedArrays(trialData); try { await updateDoc(this.docRef, { trials: arrayUnion(flattenedData) }); this.numberOfWrites++; console.log("[FirebaseManager] Added trial data:", flattenedData); } catch (error) { console.error("[FirebaseManager] Error storing trial data:", error); throw new Error("Failed to store trial data"); } } /** * Gets the total number of writes to Firestore * @returns The number of write operations performed */ getNumberOfOperations() { return this.numberOfWrites; } /** * Creates a callback function for jsPsych's on_data_update event * @returns A function that handles trial data updates */ createDataUpdateCallback() { return (data) => { if (data.no_upload) { delete data.no_upload; return data; } this.addTrialData(data).catch((error) => { console.error("[FirebaseManager] Error in data update callback:", error); }); return data; }; } /** * Creates a callback function for jsPsych's on_finish event * @returns A function that handles experiment completion */ createFinishCallback() { return () => { console.log("[FirebaseManager] Total writes to Firestore:", this.getNumberOfOperations()); }; } /** * Flattens nested arrays in an object to make it Firestore-compatible * @param obj The object to flatten * @returns A new object with flattened arrays */ flattenNestedArrays(obj) { const result = { ...obj }; for (const key in result) { const value = result[key]; if (Array.isArray(value)) { result[key] = value.reduce((acc, val, i) => { acc[i] = val; return acc; }, {}); } else if (typeof value === "object" && value !== null) { result[key] = this.flattenNestedArrays(value); } } return result; } }; export { FirebaseManager };