UNPKG

@lookit/lookit-initjspsych

Version:

This package overloads jsPsych's init function.

1 lines 31.4 kB
{"version":3,"file":"index.cjs","sources":["../src/errors.ts","../src/utils.ts","../src/index.ts"],"sourcesContent":["/** When a trial type is accidentally undefined. */\nexport class UndefinedTypeError extends Error {\n /**\n * Inform user that one of their timeline trial objects is missing the type\n * parameter.\n *\n * @param object - Timeline object with a type key whose value is\n * null/undefined.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n public constructor(object: Record<string, any>) {\n super(\n `A trial object in the timeline has an undefined type. Maybe the type name is misspelled, or the plugin you want to use is not supported. Object: ${JSON.stringify(object)}.`,\n );\n }\n}\n\n/** When a timeline element is incorrectly formatted. */\nexport class UndefinedTimelineError extends Error {\n /**\n * Inform user that one of the elements on their timeline is formatted\n * incorrectly.\n *\n * @param el - Element in the timeline. Likely a timeline node with an\n * incorrect timeline value, or trial object with missing type key/value.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n public constructor(el: any) {\n super(\n `An element in the timeline is not structured correctly or is missing required information. It may be a timeline node with a timeline array that is the wrong type or missing/undefined, or a trial object with a missing type. Element: ${JSON.stringify(el)}`,\n );\n }\n}\n\n/**\n * Error when the jsPsych instance is not accessible in the on data update\n * callback closure.\n */\nexport class NoJsPsychInstanceError extends Error {\n /**\n * Error when the jsPsych instance is not available in the on data update\n * callback closure. The instance needs to be passed into the actual\n * on_data_update callback in order to get all of the experiment data.\n */\n public constructor() {\n super(\"No jsPsych instance available for on_data_update.\");\n this.name = \"NoJsPsychInstanceError\";\n }\n}\n","import Api from \"@lookit/data\";\nimport {\n ChsRecordingData,\n JsPsychExpData,\n LookitWindow,\n} from \"@lookit/data/dist/types\";\nimport chsTemplates from \"@lookit/templates\";\nimport { DataCollection, JsPsych } from \"jspsych\";\nimport { NoJsPsychInstanceError } from \"./errors\";\nimport { UserFuncOnDataUpdate, UserFuncOnFinish } from \"./types\";\n\ndeclare let window: LookitWindow;\n\n/**\n * Minimal shape of the session recorder stored in window.chs.sessionRecorder\n * that this wrapper needs. Declared locally to avoid a dependency on the record\n * package (which is what actually constructs it).\n */\ninterface ChsSessionRecorder {\n getSessionTrialRecordingData: () => ChsRecordingData;\n}\n\n/**\n * If a session (cross-trial) recording is currently active, capture its\n * per-trial recording data (filename, is_session_recording, stream time) at the\n * current moment. Intended to be called at the start of each trial, so the\n * captured stream time reflects the trial's start.\n *\n * @returns The session recording data for this trial, or null if no session\n * recording is active.\n */\nexport const get_session_recording_data = (): ChsRecordingData | null => {\n const sessionRecorder = window.chs.sessionRecorder as\n | ChsSessionRecorder\n | null\n | undefined;\n return sessionRecorder\n ? sessionRecorder.getSessionTrialRecordingData()\n : null;\n};\n\n/**\n * Attach captured session recording data to a trial's data, unless the trial\n * already has its own recording data (e.g. a start-record or trial-record\n * trial, whose block takes precedence).\n *\n * @param data - The trial's jsPsych data (mutated in place).\n * @param recordingData - Session recording data captured at the trial's start,\n * or null if there was none.\n */\nexport const add_session_recording_data = (\n data: JsPsychExpData,\n recordingData: ChsRecordingData | null,\n): void => {\n if (recordingData && !data.chs_recording) {\n data.chs_recording = recordingData;\n }\n};\n\n/**\n * Retry an async function with exponential backoff. Used for API calls we\n * cannot afford to silently give up on (e.g. persisting the final response\n * data), where we'd rather make the participant wait than lose data.\n *\n * @param fn - Function returning the promise to retry on failure.\n * @param options - Retry configuration.\n * @param options.retries - Max number of retry attempts after the first try.\n * @param options.baseDelayMs - Delay before the first retry; doubles each\n * subsequent attempt.\n * @returns Resolved value of fn(), once it succeeds.\n */\nconst retryWithBackoff = async <T>(\n fn: () => Promise<T>,\n {\n retries = 3,\n baseDelayMs = 1000,\n }: { retries?: number; baseDelayMs?: number } = {},\n): Promise<T> => {\n for (let attempt = 0; ; attempt++) {\n try {\n return await fn();\n } catch (err) {\n if (attempt >= retries) {\n throw err;\n }\n await new Promise((resolve) =>\n setTimeout(resolve, baseDelayMs * 2 ** attempt),\n );\n }\n }\n};\n\n/**\n * Function that returns a function to be used in place of jsPsych's option\n * \"on_data_update\". \"userFunc\" should be the user's implementation of\n * \"on_data_update\". Since this is the data that is returned from each trial,\n * this function will get the collected trial data and append the current data\n * point.\n *\n * @param jsPsychInstance - JsPsych instance\n * @param responseUuid - Response UUID.\n * @param userFunc - \"on data update\" function provided by researcher.\n * @returns On data update function.\n */\nexport const on_data_update = (\n jsPsychInstance: JsPsych | undefined | null,\n responseUuid: string,\n userFunc?: UserFuncOnDataUpdate,\n) => {\n return async function (data: JsPsychExpData) {\n if (!jsPsychInstance || !jsPsychInstance.data) {\n throw new NoJsPsychInstanceError();\n }\n\n await Api.updateResponse(responseUuid, {\n exp_data: jsPsychInstance.data.get().values() as JsPsychExpData[],\n });\n await Api.finish();\n\n // Don't call the function if not defined by user.\n if (typeof userFunc === \"function\") {\n userFunc(data);\n }\n };\n};\n\n/**\n * Function that returns a function to be used in place of jsPsych's option\n * \"on_finish\". \"userFunc\" should be the user's implementation of \"on_finish\".\n * Since this is point where the experiment has ended, the function will set\n * \"completed\" to true and overwrites all experiment data with the full set of\n * collected data. Once the user function has been ran, this will redirect to\n * the study's exit url.\n *\n * @param jsPsychInstance - JsPsych instance\n * @param responseUuid - Response UUID.\n * @param userFunc - \"on finish\" function provided by the researcher.\n * @returns On finish function.\n */\nexport const on_finish = (\n jsPsychInstance: JsPsych | undefined | null,\n responseUuid: string,\n userFunc?: UserFuncOnFinish,\n) => {\n return async function (data: DataCollection) {\n // add loading animation while data/video saving finishes\n if (!jsPsychInstance || !jsPsychInstance.getDisplayElement) {\n throw new NoJsPsychInstanceError();\n }\n jsPsychInstance.getDisplayElement().innerHTML =\n chsTemplates.loadingAnimation();\n\n const exp_data: JsPsychExpData[] = data.values();\n\n const { exit_url } = window.chs.study.attributes;\n\n // Don't call the function if not defined by user.\n if (typeof userFunc === \"function\") {\n userFunc(data);\n }\n\n // Persist the final response data (including completed: true). This is\n // retried with backoff rather than given up on after one failure. Note this\n // always sends the *complete* exp_data array, so a successful retry here\n // also recovers from any earlier trial's on_data_update having failed.\n //\n // Known quirk: exp_data and completed: true are written together in a\n // single final request. If this update fails after all retries, it's possible\n // for the full data set to have been saved by an earlier (un-awaited)\n // on_data_update even though the response is not marked completed: true. We\n // accept this because having the full data matters more than the completed flag,\n // and researchers can manually override the tallied status. Decided NOT to\n // fall back to a {completed:true}-only update here because prior\n // on_data_update saves aren't awaited and so can't be assumed successful.\n try {\n await retryWithBackoff(() =>\n Api.updateResponse(responseUuid, {\n exp_data,\n completed: true,\n }),\n );\n await Api.finish();\n } catch (err) {\n console.error(\n \"Error while saving final response data after retries: \",\n err,\n );\n }\n\n // Wait for pending recording uploads independently of the response data\n // update, so a failure/retry exhaustion in one doesn't prevent us from\n // waiting on (and finding out about) the other. (Note that pending uploads\n // will still be uploading during the response-data-saving wait time,\n // this just waits further for any upload requests that are still pending\n // after response data saving is complete.)\n try {\n if (window.chs.pendingUploads) {\n const uploads = window.chs.pendingUploads;\n const results = await Promise.allSettled(uploads.map((u) => u.promise));\n results.forEach((result, i) => {\n if (result.status === \"rejected\") {\n console.error(\n `Pending upload failed for \"${uploads[i].file}\": `,\n result.reason,\n );\n }\n });\n\n // Record each upload's outcome in the trial data, matched by filename,\n // so the saved response shows which recordings uploaded successfully.\n // This happens after the response data save above because upload\n // outcomes aren't known until the uploads settle.\n let annotatedAny = false;\n uploads.forEach((upload) => {\n // Require chs_recording to exist so trials without recording data\n // never match (and so the assignment below is safe) — e.g. a match\n // between two undefined filenames.\n const trials = exp_data.filter(\n (trial) =>\n trial.chs_recording !== undefined &&\n trial.chs_recording.filename === upload.file,\n );\n if (trials.length === 0) {\n console.warn(\n `No trial data found for uploaded recording \"${upload.file}\"; its upload status was not recorded in the data.`,\n );\n }\n trials.forEach((trial) => {\n trial.chs_recording!.upload_status = upload.status;\n if (upload.error_message) {\n trial.chs_recording!.upload_error = upload.error_message;\n }\n annotatedAny = true;\n });\n });\n\n // Persist the upload outcomes in a second write (they weren't known at\n // the first save). Supplementary, so a single attempt: if it fails, the\n // full data and completed flag from the first save still stand.\n if (annotatedAny) {\n try {\n await Api.updateResponse(responseUuid, {\n exp_data,\n completed: true,\n });\n await Api.finish();\n } catch (err) {\n console.error(\n \"Error while saving recording upload statuses: \",\n err,\n );\n }\n }\n }\n } catch (err) {\n console.error(\"Error while waiting for pending uploads: \", err);\n }\n\n if (exit_url) {\n let url: URL;\n try {\n url = new URL(exit_url);\n } catch {\n try {\n url = new URL(`https://${exit_url}`);\n } catch {\n url = new URL(window.location.origin);\n }\n }\n const hash_child_id = window.chs.response.attributes.hash_child_id;\n if (hash_child_id) url.searchParams.set(\"child\", hash_child_id);\n url.searchParams.set(\"response\", window.chs.response.id);\n window.location.replace(url.toString());\n }\n };\n};\n","import { ChsRecordingData, JsPsychExpData } from \"@lookit/data/dist/types\";\nimport type { DataCollection, JsPsych as JsPsychType } from \"jspsych\";\nimport * as jspsychModule from \"jspsych\";\nimport type { TimelineArray, TrialDescription } from \"jspsych/src/timeline\";\nimport { UndefinedTimelineError, UndefinedTypeError } from \"./errors\";\nimport type {\n ChsJsPsych,\n ChsTimelineArray,\n ChsTimelineDescription,\n ChsTrialDescription,\n JsPsychOptions,\n} from \"./types\";\nimport {\n add_session_recording_data,\n get_session_recording_data,\n on_data_update,\n on_finish,\n} from \"./utils\";\n\n/**\n * Checks if the given description is a timeline array or description (node),\n * both of which might contain trial descriptions. Modified from\n * isTimelineDescription in jspsych/src/timeline to exclude trial descriptions\n * with nested timelines (jsPsych returns true for trial description objects\n * that have a \"type\" property and a nested timelines, but we need to return\n * false.)\n *\n * @param description - The description array or object to check.\n * @returns True if the description is a timeline array or timeline description\n * (object with \"timeline\" key but no \"type\" key), otherwise false.\n */\nconst isTimelineNodeArray = (\n description: ChsTrialDescription | ChsTimelineDescription | ChsTimelineArray,\n) => {\n return (\n (Boolean((description as ChsTimelineDescription).timeline) ||\n Array.isArray(description)) &&\n !(description as ChsTimelineDescription).type\n );\n};\n\n/**\n * Checks if the description is an object that contains a \"type\" key, whose\n * value is a plugin class. Returns true even when the trial object contains a\n * nested timeline. Modified from isTrialDescription in jspsych/src/timeline to\n * return true for trial descriptions with nested timelines.\n *\n * @param description - The description object to check.\n * @returns True if the description is an object with a \"type\" property,\n * otherwise false.\n */\nconst isTrialWithType = (\n description: ChsTrialDescription | ChsTimelineDescription,\n) => {\n return (\n typeof description === \"object\" &&\n !isTimelineNodeArray(\n description as ChsTrialDescription | ChsTimelineDescription,\n )\n );\n};\n\n/**\n * Function that returns a function to replace jsPsych's initJsPsych.\n *\n * @param responseUuid - Response UUID.\n * @returns InitJsPsych function.\n */\nconst lookitInitJsPsych = (responseUuid: string) => {\n return function (opts?: JsPsychOptions): ChsJsPsych {\n // Omit on_data_update from user-defined options that will be passed into origInitJsPsych.\n // We are using a closure in the on_data_update function so that we can reference the jsPsych instance,\n // and the user-defined function will be passed in through that closure.\n const {\n on_data_update: userOnDataUpdate,\n on_finish: userOnFinish,\n on_trial_start: userOnTrialStart,\n on_trial_finish: userOnTrialFinish,\n ...otherOpts\n } = opts || {};\n\n // Create a placeholder for the instance - needed for use in the onDataUpdate closure.\n let jsPsychInstance: JsPsychType | null = null;\n\n // Holds the session recording data captured at the current trial's start,\n // to be written into that trial's data when it finishes.\n let sessionTrialRecordingData: ChsRecordingData | null = null;\n\n /**\n * On_trial_start hook: capture the session recording's stream time at the\n * start of each trial (if a session recording is active), then run the\n * user's on_trial_start if provided.\n *\n * @param trial - The trial object jsPsych is starting.\n */\n const onTrialStart = (trial: TrialDescription) => {\n sessionTrialRecordingData = get_session_recording_data();\n userOnTrialStart?.(trial);\n };\n\n /**\n * On_trial_finish hook: attach the captured session recording data to the\n * finished trial's data, then run the user's on_trial_finish if provided.\n *\n * @param data - The finished trial's data.\n */\n const onTrialFinish = (data: JsPsychExpData) => {\n add_session_recording_data(data, sessionTrialRecordingData);\n sessionTrialRecordingData = null;\n userOnTrialFinish?.(data);\n };\n\n /**\n * Closure to return the on_data_update function, with the actual instance,\n * once the instance is created.\n *\n * @param args - Arguments passed to onDataUpdate\n * @returns The on_data_update function to be used\n */\n const onDataUpdate = (...args: [JsPsychExpData]) => {\n // Call the custom CHS on_data_update fn with the jsPsych instance, response UUID,\n // and the user-defined on_data_update function if it exists.\n // No checks for jsPsychInstance here because on_data_update handles that.\n return on_data_update(\n jsPsychInstance,\n responseUuid,\n userOnDataUpdate,\n )(...args);\n };\n\n /**\n * Closure to return the (experiment) on_finish function, with the actual\n * instance, once the instance is created.\n *\n * @param args - Arguments passed to onFinish\n * @returns The on_finish function to be used\n */\n const onFinish = (...args: [DataCollection]) => {\n // Call the custom CHS on_finish fn with the jsPsych instance, response UUID,\n // and the user-defined on_finish function if it exists.\n // No checks for jsPsychInstance here because on_finish handles that.\n return on_finish(jsPsychInstance, responseUuid, userOnFinish)(...args);\n };\n\n // Create the jsPsych instance and pass in the callbacks\n const jsPsych = jspsychModule.initJsPsych({\n ...otherOpts,\n on_data_update: onDataUpdate,\n on_finish: onFinish,\n on_trial_start: onTrialStart,\n on_trial_finish: onTrialFinish,\n });\n\n // Now set the instance variable to the actual instance, so that it is referenced inside onDataUpdate.\n jsPsychInstance = jsPsych;\n\n const origJsPsychRun = jsPsych.run;\n\n const lookitJsPsych = jsPsych as ChsJsPsych;\n\n /**\n * Overriding default jsPsych run function. This will allow us to\n * check/alter the timeline before running an experiment.\n *\n * @param timeline - Array of jsPsych trials (descriptions) and/or timeline\n * nodes (descriptions).\n * @returns Original jsPsych run function.\n */\n lookitJsPsych.run = async function (timeline: ChsTimelineArray) {\n /**\n * Iterate over a timeline and recursively locate any trial descriptions\n * (objects with a \"type\" key, whose value is a plugin class). For each\n * trial description, call the callback function that receives the trial\n * description as an argument.\n *\n * @param timeline - CHS versions of the jsPsych timeline array or\n * timeline description\n * @param callback - Callback function that handles each plugin class,\n * which receives as an argument the plugin class from the trial\n * description \"type\".\n * @returns Timeline array\n */\n const handleTrialTypes = (\n timeline: ChsTimelineArray | ChsTimelineDescription,\n callback: (trial: ChsTrialDescription) => void,\n ): ChsTimelineArray => {\n return timeline.map(\n (\n el: ChsTimelineDescription | ChsTrialDescription | ChsTimelineArray,\n ) => {\n // First check for timeline descriptions: arrays or objects with 'timeline' key that do not also have a 'type' key.\n if (\n isTimelineNodeArray(\n el as\n | ChsTrialDescription\n | ChsTimelineDescription\n | ChsTimelineArray,\n )\n ) {\n if (Array.isArray(el)) {\n return handleTrialTypes(el as ChsTimelineArray, callback);\n } else if (\"timeline\" in el && Array.isArray(el.timeline)) {\n const chsTimelineDescription: ChsTimelineDescription = {\n ...el,\n timeline: handleTrialTypes(\n el.timeline as ChsTimelineArray,\n callback,\n ),\n };\n return chsTimelineDescription;\n } else {\n throw new UndefinedTimelineError(el);\n }\n } else if (\n isTrialWithType(\n el as ChsTimelineDescription | ChsTrialDescription,\n )\n ) {\n // Now handle objects with a 'type' key. This includes trial descriptions with nested timelines, as long as they include a plugin type.\n if (\n el !== null &&\n \"type\" in el &&\n el.type !== null &&\n el.type !== undefined\n ) {\n const chsTrialDescription =\n el as unknown as ChsTrialDescription;\n callback(chsTrialDescription);\n return chsTrialDescription;\n } else {\n throw new UndefinedTypeError(el);\n }\n } else {\n throw new UndefinedTimelineError(el);\n }\n },\n ) as ChsTimelineArray;\n };\n\n // This function takes the CHS-typed timeline passed to our modified jsPsych.run and modifies it by adding data from the chsData function in each trial type.\n const modifiedTimeline: ChsTimelineArray = handleTrialTypes(\n timeline as ChsTimelineArray,\n (trial) => {\n if (\"type\" in trial) {\n if (trial.type?.chsData) {\n trial.data = { ...trial.data, ...trial.type.chsData() };\n }\n if (\n (trial.type as { info?: { name?: string } })?.info?.name ===\n \"assent-video\"\n ) {\n const originalOnFinish = trial.on_finish;\n /**\n * Wrapped on_finish that aborts the experiment when the child's\n * assent response is false.\n *\n * @param data - Trial data including the response value.\n */\n trial.on_finish = (data: Record<string, unknown>) => {\n originalOnFinish?.(data);\n if (data[\"response\"] === false) {\n jsPsych.abortExperiment();\n }\n };\n }\n }\n },\n );\n\n // Convert the CHS-typed timeline array back to the jsPsych-type version for compatibility with the original jsPsych.run function.\n return await origJsPsychRun(modifiedTimeline as TimelineArray);\n };\n\n return lookitJsPsych;\n };\n};\n\nexport default lookitInitJsPsych;\n"],"names":["jspsychModule","timeline"],"mappings":"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"}