basic grabbing of features working

This commit is contained in:
Josh de Leeuw 2021-10-08 15:08:04 -04:00
parent 7bbc2436da
commit 448c50788b
4 changed files with 190 additions and 45 deletions

17
package-lock.json generated
View File

@ -5178,6 +5178,11 @@
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
}, },
"node_modules/detect-browser": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/detect-browser/-/detect-browser-5.2.1.tgz",
"integrity": "sha512-eAcRiEPTs7utXWPaAgu/OX1HRJpxW7xSHpw4LTDrGFaeWnJ37HRlqpUkKsDm0AoTbtrvHQhH+5U2Cd87EGhJTg=="
},
"node_modules/detect-file": { "node_modules/detect-file": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz",
@ -14616,8 +14621,12 @@
} }
}, },
"packages/plugin-browser-check": { "packages/plugin-browser-check": {
"name": "@jspsych/plugin-browser-check",
"version": "1.0.0", "version": "1.0.0",
"license": "MIT", "license": "MIT",
"dependencies": {
"detect-browser": "^5.2.1"
},
"devDependencies": { "devDependencies": {
"@jspsych/config": "^1.0.0", "@jspsych/config": "^1.0.0",
"@jspsych/test-utils": "^1.0.0" "@jspsych/test-utils": "^1.0.0"
@ -16961,7 +16970,8 @@
"version": "file:packages/plugin-browser-check", "version": "file:packages/plugin-browser-check",
"requires": { "requires": {
"@jspsych/config": "^1.0.0", "@jspsych/config": "^1.0.0",
"@jspsych/test-utils": "^1.0.0" "@jspsych/test-utils": "^1.0.0",
"detect-browser": "^5.2.1"
} }
}, },
"@jspsych/plugin-call-function": { "@jspsych/plugin-call-function": {
@ -19332,6 +19342,11 @@
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
}, },
"detect-browser": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/detect-browser/-/detect-browser-5.2.1.tgz",
"integrity": "sha512-eAcRiEPTs7utXWPaAgu/OX1HRJpxW7xSHpw4LTDrGFaeWnJ37HRlqpUkKsDm0AoTbtrvHQhH+5U2Cd87EGhJTg=="
},
"detect-file": { "detect-file": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz",

View File

@ -39,5 +39,8 @@
"devDependencies": { "devDependencies": {
"@jspsych/config": "^1.0.0", "@jspsych/config": "^1.0.0",
"@jspsych/test-utils": "^1.0.0" "@jspsych/test-utils": "^1.0.0"
},
"dependencies": {
"detect-browser": "^5.2.1"
} }
} }

View File

@ -6,16 +6,61 @@ jest.useFakeTimers();
describe("browser-check", () => { describe("browser-check", () => {
test("contains data on window size", async () => { test("contains data on window size", async () => {
jest
.spyOn(navigator, "userAgent", "get")
.mockReturnValue(
"Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19"
);
const { expectFinished, getData } = await startTimeline([ const { expectFinished, getData } = await startTimeline([
{ {
type: browserCheck, type: browserCheck,
skip_features: ["vsync_rate"],
}, },
]); ]);
console.log(getData().values()[0]); await expectFinished();
expect(getData().values()[0].width).not.toBeUndefined();
expect(getData().values()[0].height).not.toBeUndefined();
});
test("contains browser data from userAgent", async () => {
jest
.spyOn(navigator, "userAgent", "get")
.mockReturnValue(
"Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19"
);
const { expectFinished, getData } = await startTimeline([
{
type: browserCheck,
skip_features: ["vsync_rate"],
},
]);
await expectFinished(); await expectFinished();
expect(getData().values()[0].window_width).not.toBeUndefined(); expect(getData().values()[0].browser).toBe("chrome");
expect(getData().values()[0].browser_version).toBe("18.0.1025");
});
test("contains OS data", async () => {
jest
.spyOn(navigator, "userAgent", "get")
.mockReturnValue(
"Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19"
);
const { expectFinished, getData } = await startTimeline([
{
type: browserCheck,
skip_features: ["vsync_rate"],
},
]);
await expectFinished();
expect(getData().values()[0].os).toBe("Android OS");
}); });
}); });

View File

@ -1,3 +1,4 @@
import { detect } from "detect-browser";
import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from "jspsych"; import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from "jspsych";
const info = <const>{ const info = <const>{
@ -10,16 +11,33 @@ const info = <const>{
type: ParameterType.STRING, type: ParameterType.STRING,
array: true, array: true,
default: [ default: [
"window_width", "width",
"window_height", "height",
"webaudio", "webaudio",
"browser", "browser",
"browser_version", "browser_version",
"mobile", "mobile",
"os", "os",
"fullscreen", "fullscreen",
"vsync_rate",
], ],
}, },
/**
* Any features listed here will be skipped, even if they appear in `features`. Useful for
* when you want to run most of the defaults.
*/
skip_features: {
type: ParameterType.STRING,
array: true,
default: [],
},
/**
* The number of animation frames to sample when calculating vsync_rate
*/
vsync_frame_count: {
type: ParameterType.INT,
default: 60,
},
/** /**
* List of inclusion criteria * List of inclusion criteria
*/ */
@ -45,51 +63,115 @@ class BrowserCheckPlugin implements JsPsychPlugin<Info> {
constructor(private jsPsych: JsPsych) {} constructor(private jsPsych: JsPsych) {}
private featureCheckFunctionsMap = new Map<string, () => any>( trial(display_element: HTMLElement, trial: TrialType<Info>) {
const featureCheckFunctionsMap = new Map<string, () => any>(
Object.entries({ Object.entries({
window_width: () => { width: () => {
return window.innerWidth; return window.innerWidth;
}, },
window_height: () => { height: () => {
return window.innerHeight; return window.innerHeight;
}, },
webaudio: () => { webaudio: () => {
// @ts-ignore
return ( return (
window.AudioContext || window.AudioContext ||
// @ts-ignore
window.webkitAudioContext || window.webkitAudioContext ||
// @ts-ignore
window.mozAudioContext || window.mozAudioContext ||
// @ts-ignore
window.oAudioContext || window.oAudioContext ||
// @ts-ignore
window.msAudioContext window.msAudioContext
); );
}, },
browser: () => { browser: () => {
return "TODO"; return detect().name;
}, },
browser_version: () => { browser_version: () => {
return "TODO"; return detect().version;
}, },
mobile: () => { mobile: () => {
return "TODO"; return /Mobi/i.test(window.navigator.userAgent);
}, },
os: () => { os: () => {
return "TODO"; return detect().os;
}, },
fullscreen: () => { fullscreen: () => {
return "TODO"; return (
document.exitFullscreen ||
// @ts-expect-error
document.webkitExitFullscreen ||
// @ts-expect-error
document.msExitFullscreen
);
},
vsync_rate: () => {
return new Promise((resolve) => {
let t0 = performance.now();
let deltas = [];
let framesToRun = trial.vsync_frame_count;
const finish = () => {
let sum = 0;
for (const v of deltas) {
sum += v;
}
resolve(1000.0 / (sum / deltas.length));
};
const nextFrame = () => {
let t1 = performance.now();
deltas.push(t1 - t0);
t0 = t1;
framesToRun--;
if (framesToRun > 0) {
requestAnimationFrame(nextFrame);
} else {
finish();
}
};
const start = () => {
t0 = performance.now();
requestAnimationFrame(nextFrame);
};
requestAnimationFrame(start);
});
}, },
}) })
); );
trial(display_element: HTMLElement, trial: TrialType<Info>) {
const feature_data = new Map<string, any>(); const feature_data = new Map<string, any>();
for (const feature of trial.features) { const feature_checks: Promise<void>[] = [];
feature_data.set(feature, this.featureCheckFunctionsMap.get(feature)()); const features_to_check = trial.features.filter((x) => !trial.skip_features.includes(x));
for (const feature of features_to_check) {
// this allows for feature check functions to be sync or async
feature_checks.push(
Promise.resolve(featureCheckFunctionsMap.get(feature)())
// Promise.resolve(featureCheckFunctionsMap.get(feature)())
// .then((feature_val)=>{
// feature_data.set(feature, feature_val);
// return;
// })
);
} }
Promise.allSettled(feature_checks).then((results) => {
for (let i = 0; i < features_to_check.length; i++) {
if (results[i].status === "fulfilled") {
// @ts-expect-error
feature_data.set(features_to_check[i], results[i].value);
} else {
feature_data.set(features_to_check[i], null);
}
}
end_trial();
});
var end_trial = () => {
const trial_data = { ...Object.fromEntries(feature_data) }; const trial_data = { ...Object.fromEntries(feature_data) };
this.jsPsych.finishTrial(trial_data); this.jsPsych.finishTrial(trial_data);
};
} }
// MINIMUM SIZE // MINIMUM SIZE