diff --git a/README.md b/README.md
index 02a3e97..407a203 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,11 @@ It has much the same API and has an additional benefit of helping [OpenStreetCam
# Donate
If you use uCaptcha in production, or want to see the project grow, consider funding the project to help pay for hosting and bandwidth costs.
+
+
XMR: `45uYweaLaasTFKdEHK9qADRZMatRE3U9vKFwT1kpaig26GnruZm1t21ipVjsC1KLeeL7sG3m8bHfhcce4tQJLDKNCFBLPar`
+
+
BTC: `bc1qjj45cuu7d8r6g83al7yrtkkmzu6ud4j9qzq3dn`
@@ -16,6 +20,7 @@ BTC: `bc1qjj45cuu7d8r6g83al7yrtkkmzu6ud4j9qzq3dn`
- [ ] Think of a way to detect if user's selection is correct answer
- [ ] Create tests
- [ ] Configure Rollup to create 2 packages 1x esm 1x cjs
+- [ ] Improve the UI
## Medium term (> 1 month)
- [ ] Increase security and accuracy of predictions
diff --git a/dev/mat.json b/dev/mat.json
index 54176bb..9e26dfe 100644
--- a/dev/mat.json
+++ b/dev/mat.json
@@ -1,46 +1 @@
-{
- "B4mXt9gEOp1r": {
- "mat": [
- 0.4,
- 0.4,
- 0.4,
- 0.4,
- 0.4,
- 0.4,
- 0.4,
- 0.4,
- 0.4,
- 0.6,
- 0.6,
- 0.6,
- 0.4,
- 0.6,
- 0.6,
- 0.6
- ],
- "nums": 1,
- "obj": 0
- },
- "U63lqPRdPinG": {
- "mat": [
- 0.30000000000000004,
- 0.30000000000000004,
- 0.30000000000000004,
- 0.30000000000000004,
- 0.30000000000000004,
- 0.30000000000000004,
- 0.30000000000000004,
- 0.30000000000000004,
- 0.30000000000000004,
- 0.7,
- 0.7,
- 0.7,
- 0.30000000000000004,
- 0.7,
- 0.7,
- 0.7
- ],
- "nums": 2,
- "obj": 0
- }
-}
\ No newline at end of file
+{}
\ No newline at end of file
diff --git a/src/R.js b/src/R.js
index e9191dc..bc4c431 100644
--- a/src/R.js
+++ b/src/R.js
@@ -11,3 +11,9 @@ export const PROJECT_ROOT = process.cwd();
export const IMAGES_FOLDER = path.join(PROJECT_ROOT, 'public', 'images');
export const MAX_SESSION_TIME = 60 * 30;
+
+export const TAGS = [
+ null,
+ 'vehicle',
+ 'house',
+]
diff --git a/src/client/client.js b/src/client/client.js
index 109094f..f5e7edb 100644
--- a/src/client/client.js
+++ b/src/client/client.js
@@ -1,7 +1,7 @@
import reloadSession from './helpers/reloadSession.js';
import getImage from './helpers/getImage.js';
import verifyImage from './helpers/verifyImage.js';
-import {createElement} from './util/document.js';
+import {createElement as ce, querySelector as qs} from './util/document.js';
/**
* Create a uCaptcha box
@@ -10,6 +10,9 @@ import {createElement} from './util/document.js';
*/
function uCaptchaBox(websiteKey) {
const styles = `
+ #ucaptcha-container {
+ font-family: Arial;
+ }
#ucaptcha-next {
border: none;
background-color: royalblue;
@@ -21,6 +24,10 @@ function uCaptchaBox(websiteKey) {
#ucaptcha-grid {
width: 384px;
height: 384px;
+ transition: .2s ease;
+ }
+ #ucaptcha-grid.blurred {
+ background: rgba(255, 255, 255, 0.4);
}
#ucaptcha-grid td {
transition: .1s ease;
@@ -33,11 +40,11 @@ function uCaptchaBox(websiteKey) {
border: 10px solid white;
}
`;
- const styleTag = createElement('style');
+ const styleTag = ce('style');
styleTag.innerHTML = styles;
document.head.appendChild(styleTag);
- const checkbox = createElement('div', {
+ const checkbox = ce('div', {
style: 'height:25px;display:inline-block',
});
checkbox.style.cursor = 'pointer';
@@ -45,29 +52,34 @@ function uCaptchaBox(websiteKey) {
checkbox.style.border = '2px solid #888';
checkbox.style.width = '25px';
- const captchaBox = createElement('div');
+ const captchaBox = ce('div');
captchaBox.appendChild(checkbox);
- const imageContainer = createElement('div', {id: 'ucaptcha-container'});
+ const captchaContainer = ce('div', {id: 'ucaptcha-container'});
- const image = createElement('img', {id: 'ucaptcha-img', style: 'position:absolute;display:block;z-index:-999'});
- imageContainer.appendChild(image);
+ const imageTagTitle = ce('h3', {id: 'ucaptcha-caption'});
+ captchaContainer.appendChild(imageTagTitle);
- const btn = createElement('button', {id: 'ucaptcha-next'});
+ const image = ce('img', {id: 'ucaptcha-img', style: 'position:absolute;display:block;z-index:-999'});
+ captchaContainer.appendChild(image);
+
+ const btn = ce('button', {id: 'ucaptcha-next'});
btn.textContent = 'Next';
- captchaBox.appendChild(imageContainer);
+ captchaBox.appendChild(captchaContainer);
captchaBox.appendChild(btn);
checkbox.onclick = async function() {
/** @type {import('../shared/models/UserSession.js').UserSession} */
const session = await reloadSession(websiteKey);
- const imageGrid = createElement('table', {id: 'ucaptcha-grid'});
+ qs('#ucaptcha-caption').innerHTML = `Select all squares with ${session.getImageTag()}s`;
+
+ const imageGrid = ce('table', {id: 'ucaptcha-grid'});
for (let i = 0; i < 4; i++) {
- const tr = createElement('tr');
+ const tr = ce('tr');
for (let ii = 0; ii < 4; ii++) {
- const td = createElement('td',
+ const td = ce('td',
{'style': 'cursor:pointer'});
td.addEventListener('click', (e)=>{
e.target.classList.toggle('selected');
@@ -82,7 +94,7 @@ function uCaptchaBox(websiteKey) {
getImage(session, captchaBox);
});
- imageContainer.appendChild(imageGrid);
+ captchaContainer.appendChild(imageGrid);
getImage(session, captchaBox);
@@ -98,8 +110,8 @@ function uCaptchaBox(websiteKey) {
* @param {string} selector
*/
export function create(websiteKey, selector) {
- // const iframe = createElement("iframe");
+ // const iframe = ce("iframe");
// iframe.setAttribute("src", "https://localhost:444/?k="+websiteKey)
- document.querySelector(selector).appendChild(uCaptchaBox(websiteKey));
+ qs(selector).appendChild(uCaptchaBox(websiteKey));
}
diff --git a/src/client/helpers/reloadSession.js b/src/client/helpers/reloadSession.js
index a727d55..6a52835 100644
--- a/src/client/helpers/reloadSession.js
+++ b/src/client/helpers/reloadSession.js
@@ -5,7 +5,7 @@ import UserSession from '../../models/UserSession.js';
* @param {string} websiteKey
* @return {Promise}
*/
-export default function initializeSession(websiteKey) {
+export default function reloadSession(websiteKey) {
return new Promise((resolve, reject)=>{
request(`/api/reload?k=${websiteKey}`)
.then((resp)=>{
diff --git a/src/client/helpers/verifyImage.js b/src/client/helpers/verifyImage.js
index b334e99..08d5893 100644
--- a/src/client/helpers/verifyImage.js
+++ b/src/client/helpers/verifyImage.js
@@ -1,7 +1,9 @@
import request from '../util/request.js';
+import UserSession from '../../models/UserSession.js';
+import {querySelector as qs} from '../util/document.js';
/**
- * @typedef { import('../../models/UserSession.js').UserSession } UserSession
+ * @typedef {import('../../models/UserSession.js')} UserSession
*/
@@ -10,7 +12,8 @@ import request from '../util/request.js';
* @param {UserSession} session
* @param {HTMLElement} captchaGrid
*/
-export async function verfiyImage(session, captchaGrid) {
+export default async function verifyImage(session, captchaGrid) {
+ captchaGrid.classList.add('blurred');
const tds = captchaGrid.querySelectorAll('td');
const selectedTds = [];
@@ -29,11 +32,15 @@ export async function verfiyImage(session, captchaGrid) {
'mat': mat,
};
- await request('/api/verify', {_method: 'POST', _body: body});
+ const resp = await request('/api/verify', {_method: 'POST', _body: body});
+
+ session.deserialize(resp);
+ qs('#ucaptcha-caption')
+ .innerHTML = `Select all squares with ${session.getImageTag()}s`;
+
+ captchaGrid.classList.remove('blurred');
selectedTds.map((td)=>{
td.classList.remove('selected');
});
}
-
-export default verfiyImage;
diff --git a/src/models/IDBSession.js b/src/models/IDBSession.js
index aa2a9e2..38db360 100644
--- a/src/models/IDBSession.js
+++ b/src/models/IDBSession.js
@@ -7,4 +7,3 @@
* @property {number} obj Image object ID
* @property {number} score How likely is the user to be a human?
*/
-export const IDBSession = {};
diff --git a/src/models/ImageObject.js b/src/models/ImageObject.js
deleted file mode 100644
index 3ca8836..0000000
--- a/src/models/ImageObject.js
+++ /dev/null
@@ -1,5 +0,0 @@
-export default [
- null,
- 'Vehicle',
- 'House',
-];
diff --git a/src/models/UserSession.js b/src/models/UserSession.js
index 218e22b..08eed5f 100644
--- a/src/models/UserSession.js
+++ b/src/models/UserSession.js
@@ -8,6 +8,9 @@ export default function UserSession() {
/** @private @type {string | undefined} */
this._websiteKey = undefined;
+ /** @private @type {string | undefined} */
+ this._imageTag = undefined;
+
/**
* Get session identifier
@@ -43,6 +46,23 @@ export default function UserSession() {
};
+ /**
+ * Get image tag as text
+ * @return {string}
+ */
+ this.getImageTag = function() {
+ return this._imageTag;
+ };
+
+ /**
+ * Set image tag as text
+ * @param {string} imageObject
+ */
+ this.setImageTag = function(imageObject) {
+ this._imageTag = imageObject;
+ };
+
+
/**
* Serialize the session into a JSON object
* @return {Array}
@@ -50,14 +70,12 @@ export default function UserSession() {
this.serialize = function() {
const sessionId = this.getSessionId();
const websiteKey = this.getWebsiteKey();
-
- if (!sessionId || !websiteKey) {
- throw Error();
- }
+ const imageTag = this.getImageTag();
return [
sessionId,
websiteKey,
+ imageTag,
];
};
@@ -65,9 +83,13 @@ export default function UserSession() {
* Deserialize session data from JSON object
* @param {Array} payload
*/
- this.deserialize = function([_sessionId, _websiteKey]) {
- console.log(_sessionId, _websiteKey);
- this.setSessionId(_sessionId);
- this.setWebsiteKey(_websiteKey);
+ this.deserialize = function([
+ sessionId,
+ websiteKey,
+ imageTag,
+ ]) {
+ this.setSessionId(sessionId);
+ this.setWebsiteKey(websiteKey);
+ this.setImageTag(imageTag);
};
}
diff --git a/src/routes/api/index.js b/src/routes/api/index.js
index d6680a3..8305748 100644
--- a/src/routes/api/index.js
+++ b/src/routes/api/index.js
@@ -49,10 +49,8 @@ router.post('/verify', async (req, res)=>{
const mat = req.body.mat;
console.log(req.body);
- await verifyImage(sessionId, mat);
- sendJson(res, {
- ok: 1,
- });
+ const session = await verifyImage(sessionId, mat);
+ sendJson(res, session.serialize());
});
export default router;
diff --git a/src/routes/api/reloadSession.js b/src/routes/api/reloadSession.js
index 46111d4..c89cd96 100644
--- a/src/routes/api/reloadSession.js
+++ b/src/routes/api/reloadSession.js
@@ -5,11 +5,11 @@ import pickRandomFile from './pickRandomFile.js';
import UserSession from '../../models/UserSession.js';
import {randomBytes} from '../../helpers/utils.js';
import {client} from '../../helpers/idb.js';
-import {MAX_SESSION_TIME} from '../../R.js';
+import {MAX_SESSION_TIME, TAGS} from '../../R.js';
-/**
- * @typedef {import('../../models/IDBSession.js').IDBSession} IDBSession
- */
+// /**
+// * @typedef {import('../../models/IDBSession.js')} IDBSession
+// */
/**
@@ -20,28 +20,27 @@ import {MAX_SESSION_TIME} from '../../R.js';
export default async function initializeSession(websiteKey, cookies) {
let randomSessionId;
- const session = new UserSession();
if (cookies[websiteKey]) {
randomSessionId = cookies[websiteKey];
-
- session.setSessionId(randomSessionId);
- session.setWebsiteKey(websiteKey);
-
- return session;
} else {
randomSessionId = randomBytes(8);
}
const image = await pickRandomFile();
+ const imageTagId = 1; // TODO: Change later to be dynamic based on `image`
+ const session = new UserSession();
+
session.setSessionId(randomSessionId);
session.setWebsiteKey(websiteKey);
+ session.setImageTag(TAGS[imageTagId]);
- /** @type {IDBSession} */
+ /** @type {import('../../models/IDBSession.js')} */
const idbPayload = {
sessionId: session.getSessionId(),
websiteKey: session.getWebsiteKey(),
image,
+ obj: imageTagId,
score: 0.5,
};
diff --git a/src/routes/api/verifyImage.js b/src/routes/api/verifyImage.js
index 1e510a2..f589762 100644
--- a/src/routes/api/verifyImage.js
+++ b/src/routes/api/verifyImage.js
@@ -1,9 +1,13 @@
import fs from 'fs';
import path from 'path';
import {client} from '../../helpers/idb.js';
-import {PROJECT_ROOT, MAX_SESSION_TIME} from '../../R.js';
+import {PROJECT_ROOT, MAX_SESSION_TIME, TAGS} from '../../R.js';
import {argmaxThresh} from '../../helpers/utils.js';
import pickRandomFile from './pickRandomFile.js';
+import UserSession from '../../models/UserSession.js';
+
+
+/** @typedef {import('../../models/IDBSession.js')} IDBSession */
/**
* @typedef {Object} FileMat
@@ -80,13 +84,12 @@ function updateMat(imageMat, userMat, image) {
*/
export default function(sessionId, userMat) {
return new Promise((resolve, reject)=>{
- client.get(sessionId, (err, result)=>{
- console.log(sessionId);
+ client.get(sessionId, (err, resp)=>{
if (err) return reject(err);
- if (!result) return reject(new Error('Session expired'));
+ if (!resp) return reject(new Error('Session expired'));
- /** @type {import('../../models/IDBSession.js').IDBSession} */
- result = JSON.parse(result);
+ /** @type {IDBSession} */
+ const result = JSON.parse(resp);
const imageMat = getMat(result.image);
const trueArgmax = argmaxThresh(imageMat.mat, 0.6).join(',');
@@ -109,13 +112,23 @@ export default function(sessionId, userMat) {
}
pickRandomFile().then((image)=>{
+ // TODO: Make me dynamic
+ const newImageTag = 1;
const update = JSON.stringify(
- Object.assign(result, {image: image}));
+ Object.assign(result, {
+ image: image,
+ obj: newImageTag,
+ }));
client.setex(sessionId, MAX_SESSION_TIME,
update, (err)=>{
if (err) return reject(err);
- resolve();
+ const session = new UserSession();
+ session.setSessionId(sessionId);
+ session.setWebsiteKey(result.websiteKey);
+ session.setImageTag(TAGS[newImageTag]);
+
+ resolve(session);
});
});
});