@river-build/sdk
Version:
For more details, visit the following resources:
146 lines • 5.81 kB
JavaScript
import { ChunkedMediaSchema, UserBioSchema, } from '@river-build/proto';
import { StreamStateView_AbstractContent } from './streamStateView_AbstractContent';
import { check } from '@river-build/dlog';
import { logNever } from './check';
import { getUserIdFromStreamId } from './id';
import { decryptDerivedAESGCM } from './crypto_utils';
import { fromBinary } from '@bufbuild/protobuf';
export class StreamStateView_UserMetadata extends StreamStateView_AbstractContent {
streamId;
streamCreatorId;
profileImage;
encryptedProfileImage;
bio;
encryptedBio;
decryptionInProgress = { bio: undefined, image: undefined };
// user_id -> device_keys, fallback_keys
deviceKeys = [];
constructor(streamId) {
super();
this.streamId = streamId;
this.streamCreatorId = getUserIdFromStreamId(streamId);
}
applySnapshot(snapshot, content, encryptionEmitter) {
// dispatch events for all device keys, todo this seems inefficient?
for (const value of content.encryptionDevices) {
this.addUserDeviceKey(value, encryptionEmitter, undefined);
}
if (content.profileImage?.data) {
this.addProfileImage(content.profileImage.data);
}
if (content.bio?.data) {
this.addBio(content.bio.data);
}
}
prependEvent(_event, _cleartext, _encryptionEmitter, _stateEmitter) {
// nothing to do
}
appendEvent(event, _cleartext, encryptionEmitter, stateEmitter) {
check(event.remoteEvent.event.payload.case === 'userMetadataPayload');
const payload = event.remoteEvent.event.payload.value;
switch (payload.content.case) {
case 'inception':
break;
case 'encryptionDevice':
this.addUserDeviceKey(payload.content.value, encryptionEmitter, stateEmitter);
break;
case 'profileImage':
this.addProfileImage(payload.content.value, stateEmitter);
break;
case 'bio':
this.addBio(payload.content.value, stateEmitter);
break;
case undefined:
break;
default:
logNever(payload.content);
}
}
addUserDeviceKey(value, encryptionEmitter, stateEmitter) {
const device = {
deviceKey: value.deviceKey,
fallbackKey: value.fallbackKey,
};
const existing = this.deviceKeys.findIndex((x) => x.deviceKey === device.deviceKey);
if (existing >= 0) {
this.deviceKeys.splice(existing, 1);
}
this.deviceKeys.push(device);
encryptionEmitter?.emit('userDeviceKeyMessage', this.streamId, this.streamCreatorId, device);
stateEmitter?.emit('userDeviceKeysUpdated', this.streamId, this.deviceKeys);
}
addProfileImage(data, stateEmitter) {
this.encryptedProfileImage = data;
stateEmitter?.emit('userProfileImageUpdated', this.streamId);
}
addBio(data, stateEmitter) {
this.encryptedBio = data;
stateEmitter?.emit('userBioUpdated', this.streamId);
}
async getProfileImage() {
// if we have an encrypted space image, decrypt it
if (this.encryptedProfileImage) {
const encryptedData = this.encryptedProfileImage;
this.encryptedProfileImage = undefined;
this.decryptionInProgress = {
...this.decryptionInProgress,
image: this.decrypt(encryptedData, (decrypted) => {
const profileImage = fromBinary(ChunkedMediaSchema, decrypted);
this.profileImage = profileImage;
return profileImage;
}, () => {
this.decryptionInProgress = {
...this.decryptionInProgress,
image: undefined,
};
}),
};
return this.decryptionInProgress.image;
}
// if there isn't an updated encrypted profile image, but a decryption is
// in progress, return the promise
if (this.decryptionInProgress.image) {
return this.decryptionInProgress.image;
}
return this.profileImage;
}
async getBio() {
// if we have an encrypted bio, decrypt it
if (this.encryptedBio) {
const encryptedData = this.encryptedBio;
this.encryptedBio = undefined;
this.decryptionInProgress = {
...this.decryptionInProgress,
bio: this.decrypt(encryptedData, (plaintext) => {
const bioPlaintext = fromBinary(UserBioSchema, plaintext);
this.bio = bioPlaintext;
return bioPlaintext;
}, () => {
this.decryptionInProgress = {
...this.decryptionInProgress,
bio: undefined,
};
}),
};
return this.decryptionInProgress.bio;
}
// if there isn't an updated encrypted bio, but a decryption is
// in progress, return the promise
if (this.decryptionInProgress.bio) {
return this.decryptionInProgress.bio;
}
return this.bio;
}
async decrypt(encryptedData, onDecrypted, cleanup) {
try {
const userId = getUserIdFromStreamId(this.streamId);
const context = userId.toLowerCase();
const plaintext = await decryptDerivedAESGCM(context, encryptedData);
return onDecrypted(plaintext);
}
finally {
cleanup();
}
}
}
//# sourceMappingURL=streamStateView_UserMetadata.js.map