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.
@@ -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 = (module.exports = express());
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
- // app.use(express.static(path.join(__dirname, 'dist')));
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('peerID');
18
- if (noID) {
19
- console.log('No peerID given.'); // TODO Breaking! Serve error
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('/keypad', checkParams, function (req, res) {
43
- res.render(path.join(__dirname, 'keypad.html'));
43
+ app.get("/keypad", checkParams, function (req, res) {
44
+ res.render(path.join(__dirname, "keypad.html"));
44
45
  });
45
- app.get('/receiver', function (req, res) {
46
- res.render(path.join(__dirname, 'receiver.html'));
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(3000);
65
- console.log('Express started on port 3000');
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.15.2",
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/main.js",
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": "webpack serve --open --config webpack.dev.js",
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
+ };