import { ParameterType } from 'jspsych'; const info = { name: 'captcha', parameters: { prompt: { type: ParameterType.HTML_STRING, default: '', }, difficulty: { type: ParameterType.INT, default: 3, }, button_label: { type: ParameterType.STRING, default: 'Submit', }, allow_refresh: { type: ParameterType.BOOL, default: true, }, require_correct: { type: ParameterType.BOOL, default: true, }, error_text: { type: ParameterType.HTML_STRING, default: 'That entry did not match, please try again.', }, }, }; class CaptchaPlugin { constructor(jsPsych) { this.jsPsych = jsPsych; this.characters = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; } trial(display_element, trial) { const start_time = performance.now(); const attempt_counts = { submission_count: 0, refresh_count: 0 }; const difficulty_settings = this.resolve_difficulty(trial.difficulty); let captcha_text = this.generate_text(difficulty_settings.length); let last_target_at_submission = captcha_text; display_element.innerHTML = this.build_markup( trial.prompt, trial.button_label, trial.allow_refresh ); const canvas_element = display_element.querySelector( '.jspsych_captcha_canvas' ); const canvas_context = canvas_element.getContext('2d'); const input_element = display_element.querySelector( '.jspsych_captcha_input' ); const button_element = display_element.querySelector( '.jspsych_captcha_submit' ); const refresh_button = display_element.querySelector( '.jspsych_captcha_refresh' ); const error_element = display_element.querySelector( '.jspsych_captcha_error' ); const draw_captcha = () => { this.draw_captcha(canvas_context, captcha_text, difficulty_settings); }; draw_captcha(); input_element.focus(); if (refresh_button) { refresh_button.addEventListener('click', () => { attempt_counts.refresh_count += 1; captcha_text = this.generate_text(difficulty_settings.length); draw_captcha(); error_element.textContent = ''; input_element.value = ''; input_element.focus(); }); } const length_hint_element = display_element.querySelector( '.jspsych_captcha_length_hint' ); const update_button_state = () => { const current_length = input_element.value.trim().length; const required_length = captcha_text.length; button_element.disabled = current_length < required_length; if (length_hint_element) { length_hint_element.textContent = current_length < required_length ? `Type ${required_length - current_length} more character(s) to continue.` : ''; } }; const finish_trial = (response_value, is_correct) => { const trial_data = { rt: performance.now() - start_time, response: response_value, participant_entry: response_value, participant_entry_length: response_value.length, captcha_target: last_target_at_submission, correct: is_correct, submission_count: attempt_counts.submission_count, manual_refreshes: attempt_counts.refresh_count, difficulty_label: difficulty_settings.label, difficulty_length: difficulty_settings.length, difficulty_level: difficulty_settings.level, }; display_element.innerHTML = ''; this.jsPsych.finishTrial(trial_data); }; const validate_response = () => { attempt_counts.submission_count += 1; const response_value = input_element.value.trim().toUpperCase(); last_target_at_submission = captcha_text; const is_correct = response_value === captcha_text; if (response_value.length < captcha_text.length) { error_element.textContent = `Please enter all ${captcha_text.length} characters shown in the captcha.`; update_button_state(); return; } if (is_correct || !trial.require_correct) { finish_trial(response_value, is_correct); return; } error_element.textContent = trial.error_text; captcha_text = this.generate_text(difficulty_settings.length); draw_captcha(); input_element.value = ''; input_element.focus(); }; button_element.addEventListener('click', validate_response); input_element.addEventListener('keydown', event => { if (event.key === 'Enter') { event.preventDefault(); validate_response(); } }); input_element.addEventListener('input', update_button_state); update_button_state(); } build_markup(prompt, button_label, allow_refresh) { const prompt_html = prompt ? `