UNPKG

@river-build/sdk

Version:

For more details, visit the following resources:

146 lines 5.81 kB
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