virtual-keypad 5.15.2 → 5.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +16 -0
- package/Procfile +1 -0
- package/dist/ExperimentPeer.js +2 -0
- package/dist/ExperimentPeer.js.map +1 -0
- package/dist/PhonePeer.js +2 -0
- package/dist/PhonePeer.js.map +1 -0
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/receiver.html +2 -0
- package/dist/server.js +16 -14
- package/package.json +11 -3
- package/src/ExperimentPeer.js +270 -0
- package/src/PhonePeer.js +394 -0
- package/src/__tests__/maxKeySize.test.js +80 -0
- package/src/keypad.css +35 -10
- package/src/keypad.js +19 -17
- package/src/main.js +2 -1
- package/src/maxKeySize.js +223 -48
- package/webpack.common.js +44 -16
package/dist/receiver.html
CHANGED
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
flex-direction: column;
|
|
65
65
|
width: 30%;
|
|
66
66
|
padding: 2rem;
|
|
67
|
+
overflow-y: auto;
|
|
67
68
|
}
|
|
68
69
|
</style>
|
|
69
70
|
</head>
|
|
@@ -152,6 +153,7 @@
|
|
|
152
153
|
changeAlphabetButton.style.display = "block";
|
|
153
154
|
changeAlphabetButton.onclick = changeAlphabet;
|
|
154
155
|
|
|
156
|
+
|
|
155
157
|
console.log("RECEIV Handshake complete");
|
|
156
158
|
},
|
|
157
159
|
() => console.log("RECEIV Connection connected"),
|
package/dist/server.js
CHANGED
|
@@ -2,22 +2,23 @@ const express = require("express");
|
|
|
2
2
|
const path = require("path");
|
|
3
3
|
const { v4: uuidv4 } = require("uuid");
|
|
4
4
|
|
|
5
|
-
var app =
|
|
5
|
+
var app = express();
|
|
6
6
|
var server = "http://localhost:3000";
|
|
7
|
+
const PORT = process.env.PORT || 3000;
|
|
7
8
|
|
|
8
9
|
// I got an error with line below if I didnt' add the extended property
|
|
9
10
|
// Don't actually know if this should be set to true or false.
|
|
10
11
|
// app.use(express.urlencoded({ extended: true }));
|
|
11
12
|
// app.use(express.json());
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
app.use(express.static(__dirname));
|
|
14
15
|
|
|
15
16
|
// Middleware to check we have all the params we need
|
|
16
17
|
const checkParams = (req, res, next) => {
|
|
17
|
-
let noID = !req.query.hasOwnProperty(
|
|
18
|
-
if (noID) {
|
|
19
|
-
console.log(
|
|
20
|
-
throw "No peerID given -- unable to connect to peer."
|
|
18
|
+
let noID = !req.query.hasOwnProperty("peerID");
|
|
19
|
+
if (noID) {
|
|
20
|
+
console.log("No peerID given."); // TODO Breaking! Serve error
|
|
21
|
+
throw "No peerID given -- unable to connect to peer.";
|
|
21
22
|
// req.query.peerID = uuidv4(); // TEMPorary and only usable to test keypadClient
|
|
22
23
|
}
|
|
23
24
|
|
|
@@ -39,28 +40,29 @@ const checkParams = (req, res, next) => {
|
|
|
39
40
|
|
|
40
41
|
// Link to the actual keypad
|
|
41
42
|
// ie this is where the user will be sent (from the QR code) on their phone
|
|
42
|
-
app.get(
|
|
43
|
-
res.render(path.join(__dirname,
|
|
43
|
+
app.get("/keypad", checkParams, function (req, res) {
|
|
44
|
+
res.render(path.join(__dirname, "keypad.html"));
|
|
44
45
|
});
|
|
45
|
-
app.get(
|
|
46
|
-
res.render(path.join(__dirname,
|
|
46
|
+
app.get("/receiver", function (req, res) {
|
|
47
|
+
res.render(path.join(__dirname, "receiver.html"));
|
|
47
48
|
});
|
|
48
49
|
|
|
49
50
|
app.use(function (err, req, res, next) {
|
|
50
51
|
res.status(err.status || 500);
|
|
51
52
|
res.send({
|
|
52
|
-
error: err.message
|
|
53
|
+
error: err.message,
|
|
53
54
|
});
|
|
54
55
|
});
|
|
55
56
|
|
|
56
57
|
app.use(function (req, res) {
|
|
57
58
|
res.status(404);
|
|
58
59
|
res.send({
|
|
59
|
-
error: "404 not found"
|
|
60
|
+
error: "404 not found",
|
|
60
61
|
});
|
|
61
62
|
});
|
|
62
63
|
|
|
63
64
|
if (!module.parent) {
|
|
64
|
-
app.listen(
|
|
65
|
-
|
|
65
|
+
app.listen(PORT, () => {
|
|
66
|
+
console.log(`Express started on port ${PORT}`);
|
|
67
|
+
});
|
|
66
68
|
}
|
package/package.json
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "virtual-keypad",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.17.0",
|
|
4
4
|
"description": "User response at a distance. Simple utility to summon forth a virtual keypad webapp.",
|
|
5
|
-
"main": "dist/
|
|
5
|
+
"main": "dist/server.js",
|
|
6
6
|
"directories": {
|
|
7
7
|
"example": "example",
|
|
8
8
|
"bin": "dist"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
12
|
-
"start": "
|
|
12
|
+
"start": "node dist/server.js",
|
|
13
|
+
"dev": "webpack serve --open --config webpack.dev.js",
|
|
13
14
|
"build": "webpack --config webpack.prod.js"
|
|
14
15
|
},
|
|
15
16
|
"keywords": [],
|
|
@@ -20,14 +21,21 @@
|
|
|
20
21
|
"url": "https://github.com/EasyEyes/virtual-keypad"
|
|
21
22
|
},
|
|
22
23
|
"dependencies": {
|
|
24
|
+
"express": "^4.18.2",
|
|
23
25
|
"peerjs": "^1.5.2",
|
|
24
26
|
"qrcode": "^1.4.4",
|
|
25
27
|
"uuid": "^8.3.2"
|
|
26
28
|
},
|
|
27
29
|
"devDependencies": {
|
|
30
|
+
"@babel/core": "^7.26.10",
|
|
31
|
+
"@babel/preset-env": "^7.26.9",
|
|
32
|
+
"babel-loader": "^10.0.0",
|
|
33
|
+
"core-js": "^3.41.0",
|
|
28
34
|
"css-loader": "^5.2.6",
|
|
29
35
|
"file-loader": "^6.2.0",
|
|
36
|
+
"jsdom": "^28.1.0",
|
|
30
37
|
"style-loader": "^2.0.0",
|
|
38
|
+
"vitest": "^4.0.18",
|
|
31
39
|
"webpack": "^5.40.0",
|
|
32
40
|
"webpack-cli": "^4.7.2",
|
|
33
41
|
"webpack-dev-server": "^4.13.2",
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
const doNothing = () => undefined;
|
|
2
|
+
export class ExperimentPeer {
|
|
3
|
+
constructor(
|
|
4
|
+
keypadParameters,
|
|
5
|
+
onDataCallback = doNothing,
|
|
6
|
+
handshakeCallback = doNothing,
|
|
7
|
+
customConnectionCallback = doNothing,
|
|
8
|
+
customCloseCallback = doNothing,
|
|
9
|
+
customErrorCallback = doNothing,
|
|
10
|
+
connectionManager = null
|
|
11
|
+
) {
|
|
12
|
+
keypadParameters = this.verifyKeypadParameters(keypadParameters);
|
|
13
|
+
this.alphabet = this.checkAlphabet(keypadParameters.alphabet);
|
|
14
|
+
this.font = keypadParameters.font;
|
|
15
|
+
this.controlButtons = keypadParameters.controlButtons;
|
|
16
|
+
this.onErrorReconnectMessage =
|
|
17
|
+
keypadParameters.onErrorReconnectMessage ??
|
|
18
|
+
"Connection lost. Please reconnect...";
|
|
19
|
+
this.targetElementId = keypadParameters.targetElementId;
|
|
20
|
+
this.onData = onDataCallback;
|
|
21
|
+
this.onHandshake = () => {
|
|
22
|
+
handshakeCallback();
|
|
23
|
+
// this._setupHeartBeatIntervals();
|
|
24
|
+
};
|
|
25
|
+
this.connectionManager = connectionManager;
|
|
26
|
+
this.name = "keypad";
|
|
27
|
+
this.lastHeartbeat = performance.now();
|
|
28
|
+
this.heartBeatInterval = undefined;
|
|
29
|
+
this.heartCheckInterval = undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
verifyKeypadParameters(keypadParameters) {
|
|
33
|
+
if (!keypadParameters.hasOwnProperty("alphabet")) {
|
|
34
|
+
console.error(
|
|
35
|
+
"Must provide 'alphabet' parameter to Receiver object. Defaulting to 'CDHKNORSVZ'"
|
|
36
|
+
);
|
|
37
|
+
keypadParameters["alphabet"] = "CDHKNORSVZ".split("");
|
|
38
|
+
} else {
|
|
39
|
+
keypadParameters["alphabet"] = this.checkAlphabet(
|
|
40
|
+
keypadParameters.alphabet
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
if (!keypadParameters.hasOwnProperty("font")) {
|
|
44
|
+
console.error(
|
|
45
|
+
"Must provide 'font' parameter to Receiver object. Defaulting to 'Arial'"
|
|
46
|
+
);
|
|
47
|
+
keypadParameters["alphabet"] = "Arial";
|
|
48
|
+
} else {
|
|
49
|
+
// FUTURE verify that the selected font is available
|
|
50
|
+
// TODO necessary to check control buttons?
|
|
51
|
+
}
|
|
52
|
+
return keypadParameters;
|
|
53
|
+
}
|
|
54
|
+
checkAlphabet = (proposedAlphabet) => {
|
|
55
|
+
let validAlphabet;
|
|
56
|
+
if (Array.isArray(proposedAlphabet)) {
|
|
57
|
+
// ARRAY : good
|
|
58
|
+
// FUTURE verify that symbols are displayable in desired font
|
|
59
|
+
validAlphabet = proposedAlphabet;
|
|
60
|
+
} else if (typeof proposedAlphabet == "string") {
|
|
61
|
+
// STRING : ok
|
|
62
|
+
if (
|
|
63
|
+
proposedAlphabet.toUpperCase() === "SPACE" ||
|
|
64
|
+
proposedAlphabet.toUpperCase() == "RETURN"
|
|
65
|
+
) {
|
|
66
|
+
validAlphabet = [proposedAlphabet];
|
|
67
|
+
} else {
|
|
68
|
+
validAlphabet = proposedAlphabet.split("");
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
// SOMETHING ELSE : bad
|
|
72
|
+
console.error(
|
|
73
|
+
"Error! Alphabet must be specified as an array of symbols, including 'RETURN', 'SPACE'"
|
|
74
|
+
);
|
|
75
|
+
validAlphabet = [];
|
|
76
|
+
}
|
|
77
|
+
// Return unique elements, see: https://stackoverflow.com/questions/11246758/how-to-get-unique-values-in-an-array
|
|
78
|
+
const uniqueValidAlphabet = [...new Set(validAlphabet)];
|
|
79
|
+
|
|
80
|
+
// Order alphabet so that if 'SPACE' and 'RETURN' are in the list, they are correctly positioned
|
|
81
|
+
if ("SPACE" in uniqueValidAlphabet) {
|
|
82
|
+
uniqueValidAlphabet = moveElementToEndOfArray(
|
|
83
|
+
uniqueValidAlphabet,
|
|
84
|
+
"SPACE"
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
if ("RETURN" in uniqueValidAlphabet) {
|
|
88
|
+
uniqueValidAlphabet = moveElementToEndOfArray(
|
|
89
|
+
uniqueValidAlphabet,
|
|
90
|
+
"RETURN"
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
return uniqueValidAlphabet;
|
|
94
|
+
};
|
|
95
|
+
_setupHeartBeatIntervals = () => {
|
|
96
|
+
this.heartBeatInterval = setInterval(
|
|
97
|
+
() =>
|
|
98
|
+
this.connectionManager?.send({ message: "Heartbeat", type: this.name }),
|
|
99
|
+
this.heartbeatIntervalMs
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
this.heartCheckInterval = setInterval(() => {
|
|
103
|
+
const timeSinceHeartbeatMs = performance.now() - this.lastHeartbeat;
|
|
104
|
+
if (timeSinceHeartbeatMs > this.ttd) {
|
|
105
|
+
console.log("Closing connection due to lack of heartbeat.");
|
|
106
|
+
this.connectionManager?.close();
|
|
107
|
+
this.connectionManager = undefined;
|
|
108
|
+
clearInterval(this.heartBeatInterval);
|
|
109
|
+
clearInterval(this.heartCheckInterval);
|
|
110
|
+
this.heartBeatInterval = undefined;
|
|
111
|
+
this.heartCheckInterval = undefined;
|
|
112
|
+
}
|
|
113
|
+
}, this.ttd);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
update = (
|
|
117
|
+
alphabet = undefined,
|
|
118
|
+
font = undefined,
|
|
119
|
+
controlButtons = undefined
|
|
120
|
+
) => {
|
|
121
|
+
// Update alphabet
|
|
122
|
+
if (typeof alphabet !== "undefined") {
|
|
123
|
+
const validAlphabet = this.checkAlphabet(alphabet);
|
|
124
|
+
if (String(this.alphabet) !== String(validAlphabet))
|
|
125
|
+
this.displayUpdate("New alphabet: " + String(validAlphabet), true); // DEBUG
|
|
126
|
+
this.alphabet = validAlphabet; // Store new alphabet
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Update font
|
|
130
|
+
// TODO check if the font is supported, somehow
|
|
131
|
+
this.font = font ?? this.font; // Store new font
|
|
132
|
+
|
|
133
|
+
this.controlButtons = controlButtons ?? this.controlButtons;
|
|
134
|
+
|
|
135
|
+
// Update keypad
|
|
136
|
+
try {
|
|
137
|
+
this.connectionManager?.send({
|
|
138
|
+
type: this.name,
|
|
139
|
+
message: "Update",
|
|
140
|
+
alphabet: this.alphabet,
|
|
141
|
+
font: this.font,
|
|
142
|
+
peerID: this.connectionManager?.peer.id,
|
|
143
|
+
controlButtons: this.controlButtons,
|
|
144
|
+
});
|
|
145
|
+
} catch (e) {
|
|
146
|
+
this.displayUpdate(
|
|
147
|
+
`Error updating! Alphabet: ${String(this.alphabet)}, font: ${
|
|
148
|
+
this.font
|
|
149
|
+
}`,
|
|
150
|
+
e
|
|
151
|
+
); // DEBUG
|
|
152
|
+
console.error(e);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
disableKeys = (whichKeys = undefined) => {
|
|
157
|
+
const message = {
|
|
158
|
+
message: "Disable",
|
|
159
|
+
type: this.name,
|
|
160
|
+
};
|
|
161
|
+
if (whichKeys) message.keys = whichKeys;
|
|
162
|
+
try {
|
|
163
|
+
this.connectionManager?.send(message);
|
|
164
|
+
} catch (e) {
|
|
165
|
+
this.displayUpdate(`Error disabling keys. Keys: ${whichKeys}`);
|
|
166
|
+
console.error(e);
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
enableKeys = (whichKeys = undefined) => {
|
|
171
|
+
const message = {
|
|
172
|
+
message: "Enable",
|
|
173
|
+
type: this.name,
|
|
174
|
+
};
|
|
175
|
+
if (whichKeys) message.keys = whichKeys;
|
|
176
|
+
try {
|
|
177
|
+
this.connectionManager?.send(message);
|
|
178
|
+
} catch (e) {
|
|
179
|
+
this.displayUpdate(`Error enabling keys. Keys: ${whichKeys}`);
|
|
180
|
+
console.error(e);
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
updateDisplayMessage = (message) => {
|
|
185
|
+
try {
|
|
186
|
+
this.connectionManager?.send({
|
|
187
|
+
type: this.name,
|
|
188
|
+
message: "UpdateHeader",
|
|
189
|
+
headerContent: message,
|
|
190
|
+
});
|
|
191
|
+
} catch (e) {
|
|
192
|
+
this.displayUpdate("Error in updating message!"); // DEBUG
|
|
193
|
+
console.error(e);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
updateFooterMessage = (message) => {
|
|
197
|
+
try {
|
|
198
|
+
this.connectionManager?.send({
|
|
199
|
+
type: this.name,
|
|
200
|
+
message: "UpdateFooter",
|
|
201
|
+
headerContent: message,
|
|
202
|
+
});
|
|
203
|
+
} catch (e) {
|
|
204
|
+
this.displayUpdate("Error in updating footer message."); // Debug
|
|
205
|
+
console.error(e);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
onMessage = (data) => {
|
|
210
|
+
if (!data || !data.message) return;
|
|
211
|
+
switch (data.message) {
|
|
212
|
+
case "Handshake":
|
|
213
|
+
this.connectionManager?.send({
|
|
214
|
+
type: this.name,
|
|
215
|
+
message: "KeypadParameters",
|
|
216
|
+
alphabet: this.alphabet,
|
|
217
|
+
controlButtons: this.controlButtons,
|
|
218
|
+
font: this.font,
|
|
219
|
+
onErrorReconnectMessage: this.onErrorReconnectMessage,
|
|
220
|
+
});
|
|
221
|
+
this.onHandshake();
|
|
222
|
+
break;
|
|
223
|
+
case "Keypress":
|
|
224
|
+
this.onData(data);
|
|
225
|
+
break;
|
|
226
|
+
// TODO factor out into keypadPeer
|
|
227
|
+
case "Heartbeat":
|
|
228
|
+
this.lastHeartbeat = performance.now();
|
|
229
|
+
break;
|
|
230
|
+
case "HeartRate":
|
|
231
|
+
this.heartbeatIntervalMs = data.heartbeatIntervalMs;
|
|
232
|
+
break;
|
|
233
|
+
default:
|
|
234
|
+
console.log("Message type: ", data.message);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
displayUpdate = (message, append = false) => {
|
|
239
|
+
// If the specified elem exists, update that elem
|
|
240
|
+
if (!!document.getElementById(this.targetElement)) {
|
|
241
|
+
const displayElement = document.getElementById(this.targetElement);
|
|
242
|
+
if (append) {
|
|
243
|
+
const oldInnerText = displayElement.innerText;
|
|
244
|
+
displayElement.innerText = message + "\n" + oldInnerText;
|
|
245
|
+
} else {
|
|
246
|
+
displayElement.innerText = message;
|
|
247
|
+
}
|
|
248
|
+
} else {
|
|
249
|
+
console.log("MESSAGE: ", message);
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
initializeKeypad = () => {
|
|
254
|
+
console.log("Initializing keypad");
|
|
255
|
+
this._setupHeartBeatIntervals();
|
|
256
|
+
//send message to receiver to initialize keypad
|
|
257
|
+
this.connectionManager?.send({
|
|
258
|
+
message: "InitializeKeypad",
|
|
259
|
+
type: this.name,
|
|
260
|
+
});
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const moveElementToEndOfArray = (array, element) => {
|
|
265
|
+
return [
|
|
266
|
+
...array.slice(0, array.indexOf(element)),
|
|
267
|
+
...array.slice(array.indexOf(element) + 1),
|
|
268
|
+
element,
|
|
269
|
+
];
|
|
270
|
+
};
|