Skip to content

Commit d0b77e2

Browse files
authored
Merge pull request #379 from webkom/web-nfc
Add web nfc support
2 parents 5acbbcd + c4ad14f commit d0b77e2

File tree

2 files changed

+105
-46
lines changed

2 files changed

+105
-46
lines changed

app/views/partials/moderator/serial_error.pug

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,10 @@
2020
target='_blank'
2121
) README.md
2222
hr
23+
br
24+
.text-center
25+
h4 Dummy card reader
26+
h5
27+
Open devtools and click
28+
= " "
29+
a(href='/moderator/create_user?dummyReader') here

client/services/cardKeyService.js

Lines changed: 98 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ module.exports = [
6565
function ($window, $location, $rootScope, alertService) {
6666
$rootScope.$on('$routeChangeStart', function () {
6767
angular.element($window).unbind('message');
68-
clearInterval($rootScope.serialInterval);
68+
clearTimeout($rootScope.serialTimeout);
6969
});
7070

7171
return {
@@ -74,64 +74,116 @@ module.exports = [
7474
angular.element($window).bind('message', function (e) {
7575
cb(e.data);
7676
});
77+
if ($window.location.href.includes('dummyReader')) {
78+
$rootScope.dummyReader = true;
79+
$window.scanCard = (cardKey) =>
80+
$window.window.postMessage(cardKey, '*');
81+
82+
$window.console.error(`VOTE DUMMY READER MODE
83+
84+
You are now in dummy reader mode of VOTE. Use the global function "scanCard" to scan a card. The function takes the card UID as the first (and only) parameter, and the UID can be both a string or a number.
85+
86+
Usage: scanCard(123) // where 123 is the cardId `);
87+
}
7788
// Open serial connections if they are not already open
78-
if (!$rootScope.serialWriter || !$rootScope.serialReader) {
89+
if (
90+
!$rootScope.serialDevice &&
91+
!$rootScope.ndef &&
92+
!$rootScope.dummyReader
93+
) {
7994
try {
80-
const port = await $window.navigator.serial.requestPort({});
81-
82-
await port.open({ baudRate: 9600 });
83-
$rootScope.serialWriter = port.writable.getWriter();
84-
$rootScope.serialReader = port.readable.getReader();
95+
if (
96+
$window.navigator.userAgent.includes('Android') &&
97+
$window.NDEFReader &&
98+
(!$window.navigator.serial ||
99+
$window.confirm(
100+
'You are using an Android device that (might) support web nfc. Click OK to use web nfc, and cancel to fallback to using a usb serial device.'
101+
))
102+
) {
103+
const ndef = new $window.NDEFReader();
104+
await ndef.scan();
105+
$rootScope.ndef = ndef;
106+
} else {
107+
const port = await $window.navigator.serial.requestPort({});
108+
await port.open({ baudRate: 9600 });
109+
$rootScope.serialDevice = {
110+
writer: port.writable.getWriter(),
111+
reader: port.readable.getReader(),
112+
};
113+
}
85114
} catch (e) {
86115
$rootScope.$apply(() => {
87116
$location.path('/moderator/serial_error');
88117
});
89118
}
90119
}
91-
if (!$rootScope.serialWriter || !$rootScope.serialReader) {
92-
return;
93-
}
94-
95-
let lastTime = 0;
96-
let lastData = 0;
97-
const onComplete = (input) => {
98-
const { valid, status, data } = parseData(input);
99-
if (valid && status == 'OK') {
100-
// Debounce
101-
if (data !== lastData || Date.now() - lastTime > 2000) {
102-
// data = card id
103-
cb(data);
104-
lastTime = Date.now();
105-
lastData = data;
120+
if ($rootScope.ndef) {
121+
$rootScope.ndef.onreading = ({ message, serialNumber }) => {
122+
const data = convertUID(serialNumber.split(':'));
123+
cb(data);
124+
};
125+
} else if ($rootScope.serialDevice) {
126+
let lastTime = 0;
127+
let lastData = 0;
128+
const onComplete = (input) => {
129+
const { valid, status, data } = parseData(input);
130+
if (valid && status == 'OK') {
131+
// Debounce
132+
if (data !== lastData || Date.now() - lastTime > 2000) {
133+
// data = card id
134+
cb(data);
135+
lastTime = Date.now();
136+
lastData = data;
137+
}
106138
}
107-
}
108-
};
139+
};
140+
const readResult = async () => {
141+
const message = [];
142+
let finished = false;
143+
// Keep reading bytes until the "end" byte is sent
144+
// The "end" byte is 0xbb
145+
while (!finished) {
146+
const { value } = await $rootScope.serialDevice.reader.read();
147+
for (let i = 0; i < value.length; i++) {
148+
// First byte in a message should be 170, otherwise ignore and keep on going
149+
if (message.length === 0 && value[i] !== 170) {
150+
continue;
151+
}
152+
// Second byte in a message should be 255, otherwise discard and keep on going
153+
if (message.length === 1 && value[i] !== 255) {
154+
// If value is 170, treat it as the first value, and keep on. Otherwise discard
155+
if (value[i] !== 170) {
156+
message.length = 0;
157+
}
158+
continue;
159+
}
109160

110-
const readResult = async () => {
111-
const message = [];
112-
let finished = false;
113-
// Keep reading bytes until the "end" byte is sent
114-
// The "end" byte is 0xbb
115-
while (!finished) {
116-
const { value } = await $rootScope.serialReader.read();
117-
for (let i = 0; i < value.length; i++) {
118-
if (value[i] == 0xbb) {
119-
finished = true;
120-
break;
161+
if (message.length > 3 && message.length >= message[2] + 4) {
162+
finished = true;
163+
break;
164+
}
165+
message.push(value[i]);
121166
}
122-
message.push(value[i]);
123167
}
124-
}
125-
onComplete(message);
126-
};
168+
onComplete(message);
169+
};
127170

128-
// Constantly send the readCardCommand and read the result.
129-
// If there is no card, the result will be an error status,
130-
// which is handled in the onComplete function
131-
$rootScope.serialInterval = setInterval(() => {
132-
$rootScope.serialWriter.write(readCardCommand);
133-
readResult();
134-
}, 500);
171+
// Constantly send the readCardCommand and read the result.
172+
// If there is no card, the result will be an error status,
173+
// which is handled in the onComplete function
174+
const runPoll = async () => {
175+
try {
176+
$rootScope.serialDevice.writer.write(readCardCommand);
177+
await readResult();
178+
} catch (e) {
179+
/* eslint no-console: 0 */
180+
console.error('Error doing card stuff', e);
181+
} finally {
182+
$rootScope.serialTimeout = setTimeout(runPoll, 150);
183+
}
184+
};
185+
runPoll();
186+
}
135187
},
136188
};
137189
},

0 commit comments

Comments
 (0)