UNPKG

@lookit/lookit-initjspsych

Version:

This package overloads jsPsych's init function.

1 lines 19.9 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 { JsPsychExpData, LookitWindow } 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 * 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 try {\n await Api.updateResponse(responseUuid, {\n exp_data,\n completed: true,\n });\n await Api.finish();\n if (window.chs.pendingUploads) {\n await Promise.allSettled(\n window.chs.pendingUploads.map((u) => u.promise),\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 } catch (err) {\n console.error(\n \"Error while finishing the experiment and saving data/video: \",\n err,\n );\n }\n };\n};\n","import { JsPsychExpData } from \"@lookit/data/dist/types\";\nimport type { DataCollection, JsPsych as JsPsychType } from \"jspsych\";\nimport * as jspsychModule from \"jspsych\";\nimport type { TimelineArray } 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 { on_data_update, on_finish } 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 ...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 /**\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 });\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 }\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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"}