jspsych-datamanager
Version:
A package to manage data for jsPsych experiments. Currently Firebase and Supabase are supported.
114 lines (113 loc) • 3.71 kB
JavaScript
// 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
};