Files
movim-rs/frontend/widgets/ChatOmemo/chatomemo_storage.js
T
2026-10-24 14:03:52 +07:00

251 lines
9.3 KiB
JavaScript

function ChatOmemoStorage() {
this.jid = USER_JID;
}
ChatOmemoStorage.prototype = {
Direction: {
SENDING: 1,
RECEIVING: 2,
},
// Storage is versionned to ensure a proper reset when changes are made
storageVersion: '2',
// Generic methods
put: function (key, value) {
if (key === undefined || value === undefined || key === null || value === null)
throw new Error("Tried to store undefined/null");
return localStorage.setObject(this.storageVersion + '.' + key, value);
},
get: function (key, defaultValue) {
if (key === null || key === undefined)
throw new Error("Tried to get value for undefined/null key");
if (this.storageVersion + '.' + key in localStorage) {
return localStorage.getObject(this.storageVersion + '.' + key);
} else {
return defaultValue;
}
},
remove: function (key) {
if (key === null || key === undefined)
throw new Error("Tried to remove value for undefined/null key");
localStorage.removeItem(this.storageVersion + '.' + key);
},
filter: function (search) {
return Object.keys(localStorage).filter(key => key.startsWith(this.storageVersion + '.' + this.jid + search));
},
// OMEMO specific methods
setIdentityKeyPair: function(identityKeyPair) {
return Promise.resolve(this.put(this.jid + '.identityKey', {
'privKey': MovimUtils.arrayBufferToBase64(identityKeyPair.privKey),
'pubKey': MovimUtils.arrayBufferToBase64(identityKeyPair.pubKey)
}));
},
getIdentityKeyPair: function () {
identityKeyPair = this.get(this.jid + '.identityKey');
if (!identityKeyPair) return Promise.reject();
return Promise.resolve({
'privKey': MovimUtils.base64ToArrayBuffer(identityKeyPair.privKey),
'pubKey': MovimUtils.base64ToArrayBuffer(identityKeyPair.pubKey)
});
},
setLocalRegistrationId: function (registrationId) {
return Promise.resolve(this.put(this.jid + '.registrationId', registrationId));
},
getLocalRegistrationId: function () {
return Promise.resolve(this.get(this.jid + '.registrationId'));
},
isTrustedIdentity: function (identifier, identityKey, direction) {
if (identifier === null || identifier === undefined) {
throw new Error("tried to check identity key for undefined/null key");
}
if (!(identityKey instanceof ArrayBuffer)) {
throw new Error("Expected identityKey to be an ArrayBuffer");
}
var trusted = this.get(this.jid + '.identityKey' + identifier);
if (trusted === undefined) {
return Promise.resolve(true);
}
// movim-rs: identities are stored per JID (not per device) so a contact with several
// devices would always be rejected. Blind Trust Before Verification, like Conversations:
// the fingerprints can still be checked and disabled in the contact drawer.
return Promise.resolve(true);
},
loadIdentityKey: function (identifier) {
if (identifier === null || identifier === undefined)
throw new Error("Tried to get identity key for undefined/null key");
return Promise.resolve(MovimUtils.base64ToArrayBuffer(this.get(this.jid + '.identityKey' + identifier)));
},
saveIdentity: function (identifier, identityKey) {
if (identifier === null || identifier === undefined)
throw new Error("Tried to put identity key for undefined/null key");
var address = new libsignal.SignalProtocolAddress.fromString(identifier);
var existing = this.get(this.jid + '.identityKey' + address.getName());
this.put(this.jid + '.identityKey' + address.getName(), MovimUtils.arrayBufferToBase64(identityKey))
if (existing && toString(identityKey) !== toString(existing)) {
return Promise.resolve(true);
} else {
return Promise.resolve(false);
}
},
loadPreKey: function (keyId) {
var res = this.get(this.jid + '.25519KeypreKey' + keyId);
if (res !== undefined) {
res = {
pubKey: MovimUtils.base64ToArrayBuffer(res.pubKey),
privKey: MovimUtils.base64ToArrayBuffer(res.privKey)
};
}
return Promise.resolve(res);
},
storePreKey: function (keyId, keyPair) {
keyPair.pubKey = MovimUtils.arrayBufferToBase64(keyPair.pubKey);
keyPair.privKey = MovimUtils.arrayBufferToBase64(keyPair.privKey);
return Promise.resolve(this.put(this.jid + '.25519KeypreKey' + keyId, keyPair));
},
removePreKey: function (keyId) {
return Promise.resolve(this.remove(this.jid + '.25519KeypreKey' + keyId));
},
loadCompleteSignedPreKey: function (keyId) {
var res = this.get(this.jid + '.25519KeysignedKey' + keyId);
if (res !== undefined) {
res = {
keyPair: {
pubKey: MovimUtils.base64ToArrayBuffer(res.keyPair.pubKey),
privKey: MovimUtils.base64ToArrayBuffer(res.keyPair.privKey),
},
signature: MovimUtils.base64ToArrayBuffer(res.signature),
keyId: keyId
};
}
return res;
},
loadSignedPreKey: function (keyId) {
return Promise.resolve(this.loadCompleteSignedPreKey(keyId).keyPair);
},
storeSignedPreKey: function (keyId, key) {
key.keyPair.pubKey = MovimUtils.arrayBufferToBase64(key.keyPair.pubKey);
key.keyPair.privKey = MovimUtils.arrayBufferToBase64(key.keyPair.privKey);
key.signature = MovimUtils.arrayBufferToBase64(key.signature);
return Promise.resolve(this.put(this.jid + '.25519KeysignedKey' + keyId, key));
},
removeSignedPreKey: function (keyId) {
return Promise.resolve(this.remove(this.jid + '.25519KeysignedKey' + keyId));
},
loadSession: function (identifier) {
return Promise.resolve(this.get(this.jid + '.session' + identifier));
},
storeSession: function (identifier, record) {
return Promise.resolve(this.put(this.jid + '.session' + identifier, record));
},
removeSession: function (identifier) {
return Promise.resolve(this.remove(this.jid + '.session' + identifier));
},
removeAllSessions: function () {
let filtered = this.filter('.session');
for (key in filtered) {
localStorage.removeItem(filtered[key]);
}
return Promise.resolve();
},
getOwnSessions: function () {
return this.filter('.session' + this.jid);
},
getSessions: function (identifier) {
return this.filter('.session' + identifier);
},
getOwnSessionsIds: function () {
return this.filter('.session' + this.jid).map(sessionId => sessionId.split('.').pop());
},
getSessionsIds: function (identifier) {
return this.filter('.session' + identifier).map(sessionId => sessionId.split('.').pop());
},
isJidResolved: function (identifier) {
return this.hasContactTombstone(identifier) || this.filter('.session' + identifier).length > 0;
},
removeAllSessionsOfJid: function (identifier) {
let filtered = this.filter('.session' + identifier);
for (id in filtered) {
localStorage.removeItem(filtered[id]);
}
this.remove(this.jid + '.contact' + identifier);
return Promise.resolve();
},
setContactState: function (jid, state) {
return Promise.resolve(this.put(this.jid + '.contact' + jid, state));
},
removeContactState: function (jid) {
return Promise.resolve(this.remove(this.jid + '.contact' + jid));
},
hasContactState: function(jid) {
return (this.get(this.jid + '.contact' + jid) !== undefined);
},
getContactState: function (jid) {
let state = Boolean(this.get(this.jid + '.contact' + jid));
return Promise.resolve(state);
},
/**
* Tombstone to know if a contact doesn't have OMEMO at the moment
*/
setContactTombstone: function (jid) {
return Promise.resolve(this.put(this.jid + '.contact.tombstone' + jid, true));
},
removeContactTombstone: function (jid) {
return Promise.resolve(this.remove(this.jid + '.contact.tombstone' + jid));
},
hasContactTombstone: function(jid) {
return (this.get(this.jid + '.contact.tombstone' + jid) == true);
},
getSessionState: function (identifier) {
return this.loadSession(identifier).then(json => {
if (json !== undefined) {
let session = JSON.parse(json);
return (session.state === true || session.state === undefined);
}
return false;
});
},
setSessionState: function (identifier, enabled) {
this.loadSession(identifier).then(json => {
if (json !== undefined) {
let session = JSON.parse(json);
session.state = Boolean(enabled);
this.storeSession(identifier, JSON.stringify(session));
}
});
},
toString: function(thing) {
if (typeof thing == 'string') {
return thing;
}
return new dcodeIO.ByteBuffer.wrap(thing).toString('binary');
}
};