mirror of
https://github.com/cryptpad/cryptpad.git
synced 2026-09-14 11:05:41 +05:00
fix(websocket): fix multiple websocket connections persisting on forms
This commit is contained in:
parent
2a3ab9056f
commit
1a541ba706
@ -5,7 +5,7 @@
|
||||
const factory = (Sortify, UserObject, ProxyManager,
|
||||
Migrate, Hash, Util, Constants, Feedback,
|
||||
Realtime, Messaging, Pinpad, Rpc, Cryptget, Cache,
|
||||
SF, AccountTS, DriveTS, PadTS, Cursor,
|
||||
SF, AccountTS, DriveTS, PadTS, Form, Cursor,
|
||||
Support, Integration, OnlyOffice,
|
||||
Mailbox, Profile, Team, Messenger, History,
|
||||
Calendar, BadgeTS, Block, NetConfig,
|
||||
@ -70,6 +70,9 @@ const factory = (Sortify, UserObject, ProxyManager,
|
||||
Store.drive = Drive.initAPI({
|
||||
Store, store, postMessage, broadcast
|
||||
});
|
||||
Store.form = Form.init({
|
||||
Store, store, postMessage, broadcast
|
||||
});
|
||||
|
||||
// Drive clients
|
||||
var driveEventClients = [];
|
||||
@ -3057,6 +3060,7 @@ module.exports = factory(
|
||||
require('./components/account'), // .ts
|
||||
require('./components/drive'), // .ts
|
||||
require('./components/pad'), // .ts
|
||||
require('./components/form'),
|
||||
require('./modules/cursor'),
|
||||
require('./modules/support'),
|
||||
require('./modules/integration'),
|
||||
|
||||
122
src/worker/components/form.js
Normal file
122
src/worker/components/form.js
Normal file
@ -0,0 +1,122 @@
|
||||
// SPDX-FileCopyrightText: 2026 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
const factory = (Util, Nacl, CPNetflux, Crypto) => {
|
||||
const Form = {};
|
||||
|
||||
const u8_slice = (A, start, end) => {
|
||||
return new Uint8Array(Array.prototype.slice.call(A, start, end));
|
||||
};
|
||||
const checkAnonProof = (proofObj, channel, curvePrivate) => {
|
||||
const pub = proofObj.key;
|
||||
const proofTxt = proofObj.proof;
|
||||
try {
|
||||
let u8_bundle = Util.decodeBase64(proofTxt);
|
||||
let u8_nonce = u8_slice(u8_bundle, 0, Nacl.box.nonceLength);
|
||||
let u8_cipher = u8_slice(u8_bundle, Nacl.box.nonceLength);
|
||||
let u8_plain = Nacl.box.open(
|
||||
u8_cipher,
|
||||
u8_nonce,
|
||||
Util.decodeBase64(pub),
|
||||
Util.decodeBase64(curvePrivate)
|
||||
);
|
||||
return channel === Util.encodeUTF8(u8_plain);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const getResponses = (ctx, data, cb) => {
|
||||
const { store, Store } = ctx;
|
||||
const {
|
||||
cryptoKeys, // encryptor keys
|
||||
channel, // form responses channel id
|
||||
edPublic, // my edPublic
|
||||
validateKey, // form responses secondaryValidateKey
|
||||
deleteLines,
|
||||
cantEdit
|
||||
} = data;
|
||||
|
||||
const curvePrivate = cryptoKeys.curvePrivate;
|
||||
const crypto = Crypto.Mailbox.createEncryptor(cryptoKeys);
|
||||
|
||||
let config = {
|
||||
network: store.network,
|
||||
channel,
|
||||
noChainPad: true,
|
||||
validateKey,
|
||||
owners: [edPublic],
|
||||
crypto: crypto,
|
||||
metadata: {
|
||||
deleteLines: true
|
||||
}
|
||||
};
|
||||
|
||||
let cpNf;
|
||||
let results = {};
|
||||
|
||||
config.onError = (info) => {
|
||||
cb({ error: info.type });
|
||||
cpNf?.stop();
|
||||
};
|
||||
config.onRejected = Store.onRejected;
|
||||
|
||||
config.onReady = function () {
|
||||
cb({ results });
|
||||
cpNf?.stop();
|
||||
};
|
||||
|
||||
config.onMessage = (msg, peer, vKey, isCp, hash, senderCurve, cfg) => {
|
||||
let parsed = Util.tryParse(msg);
|
||||
if (!parsed) { return; }
|
||||
let uid = parsed._uid || '000';
|
||||
|
||||
// If we have a "non-anonymous" answer, it may be the edition of a
|
||||
// previous anonymous answer. Check if a previous anonymous answer exists
|
||||
// with the same uid and delete it.
|
||||
if (parsed._proof) {
|
||||
const check = checkAnonProof(parsed._proof, channel, curvePrivate);
|
||||
const theirAnonKey = parsed._proof.key;
|
||||
if (check && results[theirAnonKey] && results[theirAnonKey][uid]) {
|
||||
delete results[theirAnonKey][uid];
|
||||
}
|
||||
}
|
||||
|
||||
parsed._time = cfg && cfg.time;
|
||||
if (deleteLines) { parsed._hash = hash; }
|
||||
|
||||
if (cantEdit && results[senderCurve]
|
||||
&& results[senderCurve][uid]) { return; }
|
||||
results[senderCurve] = results[senderCurve] || {};
|
||||
results[senderCurve][uid] = {
|
||||
msg: parsed,
|
||||
hash: hash,
|
||||
time: cfg && cfg.time
|
||||
};
|
||||
};
|
||||
cpNf = CPNetflux.start(config);
|
||||
};
|
||||
|
||||
Form.init = (config) => {
|
||||
const { store, Store } = config;
|
||||
|
||||
const ctx = { store, Store };
|
||||
const form = {}
|
||||
|
||||
form.getResponses = (cId, data, cb) => {
|
||||
return getResponses(ctx, data, cb);
|
||||
};
|
||||
|
||||
return form;
|
||||
};
|
||||
|
||||
return Form;
|
||||
};
|
||||
module.exports = factory(
|
||||
require("../../common/common-util"),
|
||||
require('tweetnacl/nacl-fast'),
|
||||
require("chainpad-netflux"),
|
||||
require("chainpad-crypto")
|
||||
);
|
||||
@ -85,6 +85,8 @@ const factory = AStore => {
|
||||
CHANGE_PAD_PASSWORD_PIN: Store.changePadPasswordPin,
|
||||
GET_SNAPSHOT: Store.getSnapshot,
|
||||
DELETE_MAILBOX_MESSAGE: Store.deleteMailboxMessage,
|
||||
// Form
|
||||
FORM_GET_RESPONSES: Store.form?.getResponses,
|
||||
// Drive
|
||||
DRIVE_USEROBJECT: Store.userObjectCommand,
|
||||
GET_DRIVE: Store.drive.get,
|
||||
|
||||
@ -289,6 +289,10 @@ define([
|
||||
});
|
||||
});
|
||||
};
|
||||
common.getFormResponses = (data, cb) => {
|
||||
postMessage("FORM_GET_RESPONSES", data, cb);
|
||||
};
|
||||
|
||||
common.muteChannel = function (channel, state, cb) {
|
||||
var mutedChannels = [];
|
||||
nThen(function (waitFor) {
|
||||
|
||||
2
www/common/worker.bundle.min.js
vendored
2
www/common/worker.bundle.min.js
vendored
File diff suppressed because one or more lines are too long
@ -135,28 +135,6 @@ define([
|
||||
});
|
||||
});
|
||||
});
|
||||
var u8_slice = function (A, start, end) {
|
||||
return new Uint8Array(Array.prototype.slice.call(A, start, end));
|
||||
};
|
||||
var checkAnonProof = function (proofObj, channel, curvePrivate) {
|
||||
var pub = proofObj.key;
|
||||
var proofTxt = proofObj.proof;
|
||||
try {
|
||||
var u8_bundle = Util.decodeBase64(proofTxt);
|
||||
var u8_nonce = u8_slice(u8_bundle, 0, Nacl.box.nonceLength);
|
||||
var u8_cipher = u8_slice(u8_bundle, Nacl.box.nonceLength);
|
||||
var u8_plain = Nacl.box.open(
|
||||
u8_cipher,
|
||||
u8_nonce,
|
||||
Util.decodeBase64(pub),
|
||||
Util.decodeBase64(curvePrivate)
|
||||
);
|
||||
return channel === Util.encodeUTF8(u8_plain);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
sframeChan.on('Q_FORM_FETCH_ANSWERS', function (data, _cb) {
|
||||
var formHref = data.href;
|
||||
var cb = Utils.Util.once(_cb);
|
||||
@ -178,7 +156,6 @@ define([
|
||||
Cryptpad.getAccessKeys(w(function (_keys) {
|
||||
if (!Array.isArray(_keys)) { return; }
|
||||
accessKeys = _keys;
|
||||
|
||||
_keys.some(function (_k) {
|
||||
if ((personalDrive && !_k.id) || Cryptpad.initialTeam === Number(_k.id)) {
|
||||
myKeys = _k;
|
||||
@ -194,14 +171,10 @@ define([
|
||||
}
|
||||
myFormKeys = keys;
|
||||
}));
|
||||
Cryptpad.makeNetwork(w(function (err, nw) {
|
||||
network = nw;
|
||||
}));
|
||||
Cryptpad.getPadMetadata({channel: data.channel}, w(function (md) {
|
||||
if (md && md.deleteLines) { deleteLines = true; }
|
||||
}));
|
||||
}).nThen(function () {
|
||||
if (!network) { return void cb({error: "E_CONNECT"}); }
|
||||
if (myFormKeys.formSeed) {
|
||||
myFormKeys = Cryptpad.getAnonymousKeys(myFormKeys.formSeed, data.channel, Utils);
|
||||
}
|
||||
@ -220,86 +193,33 @@ define([
|
||||
publicKey = formData?.form_public;
|
||||
var curvePrivate = privateKey || data.privateKey;
|
||||
if (!curvePrivate) { return void cb({error: 'EFORBIDDEN'}); }
|
||||
var crypto = Utils.Crypto.Mailbox.createEncryptor({
|
||||
var cryptoKeys = {
|
||||
curvePrivate: curvePrivate,
|
||||
curvePublic: publicKey || data.publicKey,
|
||||
validateKey: data.validateKey
|
||||
});
|
||||
};
|
||||
|
||||
var config = {
|
||||
network: network,
|
||||
Cryptpad.getFormResponses({
|
||||
cryptoKeys,
|
||||
channel: data.channel,
|
||||
noChainPad: true,
|
||||
edPublic: myKeys.edPublic,
|
||||
validateKey: keys.secondaryValidateKey,
|
||||
owners: [myKeys.edPublic],
|
||||
crypto: crypto,
|
||||
metadata: {
|
||||
deleteLines: true
|
||||
}
|
||||
//Cache: Utils.Cache // TODO enable cache for form responses when the cache stops evicting old answers
|
||||
};
|
||||
var results = {};
|
||||
config.onError = function (info) {
|
||||
cb({ error: info.type });
|
||||
};
|
||||
config.onRejected = function (data, cb) {
|
||||
if (!Array.isArray(data) || !data.length || data[0].length !== 16) {
|
||||
return void cb(true);
|
||||
}
|
||||
if (!Array.isArray(accessKeys)) { return void cb(true); }
|
||||
network.historyKeeper = data[0];
|
||||
nThen(function (waitFor) {
|
||||
accessKeys.forEach(function (obj) {
|
||||
Pinpad.create(network, obj, waitFor(function (e) {
|
||||
if (e) { console.error(e); }
|
||||
}));
|
||||
});
|
||||
}).nThen(function () {
|
||||
cb();
|
||||
});
|
||||
};
|
||||
config.onReady = function () {
|
||||
var myKey;
|
||||
cantEdit: data.cantEdit,
|
||||
deleteLines
|
||||
}, (obj) => {
|
||||
if (obj?.error) { return void cb(obj); }
|
||||
const results = obj.results;
|
||||
|
||||
// If we have submitted an anonymous answer, retrieve it
|
||||
let myKey;
|
||||
if (myFormKeys.curvePublic && results[myFormKeys.curvePublic]) {
|
||||
myKey = myFormKeys.curvePublic;
|
||||
}
|
||||
|
||||
cb({
|
||||
noDriveAnswered: noDriveAnswered,
|
||||
myKey: myKey,
|
||||
results: results
|
||||
noDriveAnswered, myKey, results
|
||||
});
|
||||
network.disconnect();
|
||||
};
|
||||
config.onMessage = function (msg, peer, vKey, isCp, hash, senderCurve, cfg) {
|
||||
var parsed = Utils.Util.tryParse(msg);
|
||||
if (!parsed) { return; }
|
||||
var uid = parsed._uid || '000';
|
||||
|
||||
// If we have a "non-anonymous" answer, it may be the edition of a
|
||||
// previous anonymous answer. Check if a previous anonymous answer exists
|
||||
// with the same uid and delete it.
|
||||
if (parsed._proof) {
|
||||
var check = checkAnonProof(parsed._proof, data.channel, curvePrivate);
|
||||
var theirAnonKey = parsed._proof.key;
|
||||
if (check && results[theirAnonKey] && results[theirAnonKey][uid]) {
|
||||
delete results[theirAnonKey][uid];
|
||||
}
|
||||
}
|
||||
|
||||
parsed._time = cfg && cfg.time;
|
||||
if (deleteLines) { parsed._hash = hash; }
|
||||
|
||||
if (data.cantEdit && results[senderCurve]
|
||||
&& results[senderCurve][uid]) { return; }
|
||||
results[senderCurve] = results[senderCurve] || {};
|
||||
results[senderCurve][uid] = {
|
||||
msg: parsed,
|
||||
hash: hash,
|
||||
time: cfg && cfg.time
|
||||
};
|
||||
};
|
||||
CPNetflux.start(config);
|
||||
});
|
||||
});
|
||||
});
|
||||
var noDriveSeed = Utils.Hash.createChannelId();
|
||||
|
||||
@ -25,6 +25,7 @@ define([
|
||||
'/common/diffMarked.js',
|
||||
'/common/sframe-common-codemirror.js',
|
||||
'/common/text-cursor.js',
|
||||
'/common/visible.js',
|
||||
'cm/lib/codemirror',
|
||||
'/components/chainpad/chainpad.dist.js',
|
||||
'tui-date-picker',
|
||||
@ -73,6 +74,7 @@ define([
|
||||
DiffMd,
|
||||
SFCodeMirror,
|
||||
TextCursor,
|
||||
Visible,
|
||||
CMeditor,
|
||||
ChainPad,
|
||||
DatePicker,
|
||||
@ -3280,6 +3282,8 @@ define([
|
||||
h('span.cp-button-name', Messages._getKey('form_results', [l])),
|
||||
]));
|
||||
var it = setInterval(function () {
|
||||
let visible = Visible.currently();
|
||||
if (!visible) { return; }
|
||||
sframeChan.query("Q_FORM_FETCH_ANSWERS", content.answers, function (err, obj) {
|
||||
var answers = obj && obj.results;
|
||||
var l = getAnswersLength(answers);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user