OverviewFilesCommitsIssuesDocsReleasesPeers

holepunchto-deps / @holepunchto / keet-core / lib / core / sync / updater.js

1 lines · javascript ref:
const e=require("ready-resource"),t=require("safety-catch"),s=require("b4a"),i=require("autobee"),a=require("./loader"),{contract:{hyperschema:{getEnum:o}}}=require("../../../schema/spec"),{verifyIdentityProofs:n,generateRoomCapabilities:r,generateIdentityPairingName:l}=require("../invitations"),{RoomStatus:h}=require("../contract/identity/private"),{KeetErrorV2:c}=require("../../errors"),d=o("@keet/room-type");module.exports=class extends e{constructor(e,t,s){super(),this.keet=e,this.swarm=e.swarm,this.local=e.local,this.view=t,this.engine=s,this.isCurrentPrivateProfile=!1,this.currentPrivateProfileInfo=null,this.currentIdentityInfo=null,this.currentWriterInfo=null,this.currentRoomInfo=null,this.currentUpdate=null,this.roomInfo=null,this.migratedFromAutobase=!1,this.updated=!1,this.processed=null,this.updates={cache:null,tasks:null,contacts:null,dispatches:null,shouldUpgrade:!1,profileDispatches:null,unreadKeys:!1,activeCall:null,activeCallDevice:null,activeCallSynced:!1,sharedRooms:null,sharedPreferences:null,pendingInvitations:null,startIdentitySwap:null,attemptIdentitySwap:null,attemptRecovery:null,finishIdentitySwap:null,logout:!1,settings:null},this.events=[],this._loader=null,this._running=null}_open(){return this._loader=new a(this.local,this.view,this.engine),this._loader.ready()}async _close(){this._running&&await this._running.catch(t),this._loader&&await this._loader.close()}async _syncDigest(e){if(e||(e=await this._loader.loadDigest()),e){if(!this.keet.isValidBuildType(e))throw c.INVALID_BUILD_TYPE();this.events.push({type:"v2:room-digest-changed",value:e}),this.roomInfo.digest=e}}async _syncConfig(){const e=await this._loader.loadConfig();e&&(this.events.push({type:"v2:room-config-changed",value:e}),this.roomInfo.config=e)}async _syncRoomCapabilities(e){e||(e=await this._loader.loadCapabilities()),e&&(this.roomInfo.seed&&s.equals(this.roomInfo.seed,e.seed)||(this.events.push({type:"v2:room-capabilities-changed",value:e}),this.roomInfo.seed=e.seed,this.roomInfo.capabilities=r(this.engine.key,e.seed)))}async _syncSelf(){const[e,t,s]=await Promise.all([this._loader.loadDigest(),this._loader.loadSelfDevice(),this._loader.loadSelfMember()]);if(this.roomInfo.selfDevice=t,this.roomInfo.selfMember=s,!e||e.settings.roomType!==d.DirectMessage)return;const[i,a]=await Promise.all([this._loader.loadConfig(),this._loader.loadDMMembers()]);this.roomInfo.config=i,this.roomInfo.dm=a}async _syncSelfMember(e){if(this.events.push({type:"v2:room-member-changed",value:e}),!e||s.equals(e.key,this.currentIdentityInfo.publicKey))return this._syncSelf()}async _syncDevice(e){if(this.events.push({type:"v2:room-device-changed",value:e}),!e||s.equals(e.key,this.engine.local.key))return this._syncSelf()}_setLastMessage(e){this.events.push({type:"v2:room-last-message-changed",value:e}),this.roomInfo.lastMessage=e.message,this.roomInfo.lastMember=e.member,this.roomInfo.lastMessageReactions=e.reactions,this.roomInfo.lastMessageLabels=e.labels,this.updates.cache={member:e.member,otherMembers:e.otherMembers,device:e.device};const t=this.currentRoomInfo?.lastMessageLabels;var i,a;i=t,a=e.labels,(i&&a?i.mine.length===a.mine.length&&i.digest.total===a.digest.total&&i.digest.editedAt===a.digest.editedAt:!i&&!a)||(this.updates.labels=e.labels);const o=this.currentRoomInfo?.lastMessage;var n,r;o&&(n=o,r=e.message,s.equals(n.oplog.key,r.oplog.key)&&n.oplog.seq===r.oplog.seq)||(this.updates.chat=e.message)}async _syncLastMessage(){const e=await this._loader.loadLastMessage();e&&this._setLastMessage(e)}async _syncMessage(e){if(!e)return;const[t,s]=await Promise.all([this._loader.loadPrimaryThread(),this._loader.loadMessage(e.id)]);0===e.id.thread&&e.id.index===t.count-1&&this._setLastMessage(s),this.events.push({type:"v2:room-chat-changed",value:s})}async _syncReactions(e){const t=await this._loader.input.get("@keet/chat",{id:e.id});this.events.push({type:"v2:room-reactions-changed",value:{id:t.oplog,reactions:e}})}async _syncChatLabels(e){const t=await this._loader.loadPrimaryThread();if(0!==e.id.thread||e.id.index!==t.count-1)return;const s=await this._loader.loadLabels(e.id);this.roomInfo.lastMessageLabels=s,this.updates.labels=s}_syncJoinRequests(e){this.events.push({type:"v2:room-join-requests-changed",value:e})}async _syncReadLengths(){const e=await this._loader.loadReadLengths();e?.length&&(this.events.push({type:"v2:read-lengths-changed",value:e}),this.roomInfo.readLengths=e)}async _syncActiveCall(){this.updates.activeCallSynced=!0;const e=await this._loader.loadActiveCall();let t=null;e&&(t=await this._loader.loadActiveCallDevice(e.callKey,this.engine.local.key),this.updates.activeCallDevice=t),this.updates.activeCall=e;let s=null;e&&(s={callKey:e.callKey,createdAt:e.createdAt,selfDeviceKey:null},t&&(s.selfDeviceKey=t.deviceKey)),this.roomInfo.activeCall=s}async _syncPreferences(){const e=await this._loader.loadPreferences();e&&(this.roomInfo.preferences=e.status)}async _syncStats(e){e||(e=await this._loader.loadPrimaryThread()),e&&(this.events.push({type:"v2:room-stats-changed",value:e}),this.roomInfo.stats=e.stats)}async _syncRoomIdentity(e){e||(e=await this._loader.loadIdentity()),e&&(this.events.push({type:"v2:room-identity-changed",value:e}),this.roomInfo.identity=e)}async _syncRoomSettings(e){e||(e=await this._loader.loadSettings()),e&&(this.roomInfo.settings=e)}async _syncUnreadAnchors(){this.updates.unreadKeys=!0}async _syncMember(e){const i=[this._syncSelfMember(e)];this.roomInfo.dm?.length&&i.push(this._syncConfig().catch(t));const{lastMember:a}=this.roomInfo;a&&s.equals(a.key,e.key)&&i.push(this._syncLastMessage().catch(t)),await Promise.all(i)}async _incrementalRoomInfoLoad(e){const s=[],i=["@keet/calls","@keet/call-devices","@keet/call-members","@keet/call-streams"];for await(const{type:a,collection:o,value:n}of this._loader.input.changes({from:e})){if(this.closing||this.keet.closing)return!1;if(!i.includes(o)&&"insert"!==a)continue;let e=null;switch(o){case"@keet/system-digest":e=this._syncDigest(n);break;case"@keet/room-config":e=this._syncConfig();break;case"@keet/room-capabilities":e=this._syncRoomCapabilities(n);break;case"@keet/members":e=this._syncMember(n);break;case"@keet/devices":e=this._syncDevice(n);break;case"@keet/chat":e=this._syncMessage(n);break;case"@keet/reaction-digests":e=this._syncReactions(n);break;case"@keet/chat-label-digests":e=this._syncChatLabels(n);break;case"@keet/threads":e=this._syncStats(n);break;case"@keet-private-id/identity":e=this._syncRoomIdentity(n);break;case"@keet-private-id/settings":e=this._syncRoomSettings(n);break;case"@keet/join-requests":e=this._syncJoinRequests(n);break;case"@keet-private-id/unreads-v2":case"@keet-private-id/unreads":e=this._syncUnreadAnchors();break;case"@keet/read-lengths":e=this._syncReadLengths();break;case"@keet/calls":case"@keet/call-devices":case"@keet/call-members":case"@keet/call-streams":e=this._syncActiveCall();break;default:continue}e&&(e.catch(t),s.push(e))}return await Promise.all(s),!!this.roomInfo.digest}async _fullRoomInfoLoad(){return this.events.push({type:"v2:room-fast-forward"}),this.roomInfo={roomKey:this.engine.key,discoveryKey:this.engine.discoveryKey,encryptionKey:this.engine.encryptionKey,digest:null,stats:null,fork:0,length:0,system:null,timestamps:{creation:0,latestActivity:0,latestChatMessage:0},status:null,mailbox:null,selfDevice:null,selfMember:null,config:null,capabilities:null,identity:null,lastMessage:null,lastMember:null,lastMessageReactions:null,lastMessageLabels:null,dm:null,activeCall:null,preferences:null,settings:null},await Promise.all([this._syncDigest(null),this._syncRoomCapabilities(null),this._syncDevice(null),this._syncMember(null),this._syncStats(null),this._syncRoomIdentity(null),this._syncUnreadAnchors(),this._syncConfig(),this._syncReadLengths(),this._syncLastMessage(),this._syncActiveCall(),this._syncPreferences(),this._syncRoomSettings()]),!!this.roomInfo.digest}async _loadPrivateProfileChanges(e){if(!0===this.keet.debugDisableSyncing)return;let t=null;if(this.isCurrentPrivateProfile&&this.roomInfo.selfDevice){const[s,i,a,o]=await Promise.all([this._loader.loadContactChanges(e),this._loader.loadSharedRoomChanges(e),this._loader.loadPendingInvitations(),this.keet.getAutobeeMigrationInfo(this.engine.key)]);t=[s,i,a],this.migratedFromAutobase=!!o}t&&(this.updates.contacts=t[0],this.updates.sharedRooms=t[1],this.updates.pendingInvitations=t[2])}async _loadLocalInfo(){const[e,t,i,a]=await Promise.all([this._loader.loadLocalRoomInfo(),this._loader.loadRoomUpdate(),this._loader.loadLocalIdentityInfo(),this._loader.loadLocalPrivateProfileInfo()]);this.currentRoomInfo=e,this.currentUpdate=t,this.roomInfo={...this.currentRoomInfo},this.currentIdentityInfo=i,this.currentPrivateProfileInfo=a,this.isCurrentPrivateProfile=!!i&&s.equals(i.privateProfileKey,this.engine.key);const o=await this.local.get("@local/pending-recovery");if(o?.identity&&o.privateProfileKey&&s.equals(o.privateProfileKey,this.engine.key))return this.isCurrentPrivateProfile=!0,this.currentIdentityInfo=o.identity,this.currentPrivateProfileInfo=e,this.updates.attemptRecovery=o.identity,!0;const n=this.currentIdentityInfo?.swapV2;return!n||!s.equals(n.privateProfileKey,this.engine.key)||(this.updates.attemptIdentitySwap=n,!0)}async _load(){let[e,t]=await Promise.all([this._loadLocalInfo(),this._loader.loadWriterInfo()]);if(this.closing||!e)return!1;if(this.updates.attemptRecovery){const e=await this._loader.loadSelfDevice();e?.status.isActive&&(this.updates.finishIdentitySwap=this.updates.attemptRecovery)}if(this.updates.attemptIdentitySwap){const e=this.updates.attemptIdentitySwap,t=e.writerKey,[i,a,o]=await Promise.all([this._loader.input.get("@keet-private-id/device-identities",{deviceKey:t}),this._loader.input.get("@keet/devices",{key:t}),this.local.get("@local/rooms",{roomKey:this.engine.key})]);if(!i||!a||!n(e,i))return!1;const r=i.deviceProof,h=l(e.publicKey),c=await this.keet.store.createKeyPair(h);if(!s.equals(c.publicKey,e.devicePublicKey))return!1;this.isCurrentPrivateProfile=!0,this.currentPrivateProfileInfo=o,this.currentIdentityInfo=this.currentIdentityInfo.swapV2,this.updates.finishIdentitySwap={publicKey:this.updates.attemptIdentitySwap.publicKey,privateProfileKey:this.engine.key,deviceKeyPair:c,deviceProof:r,status:{isConfirmed:!0}}}this.currentWriterInfo=t;const a=this.currentRoomInfo?.processed||i.GENESIS;if(e=await this._fullRoomInfoLoad(),!e)return!1;this.events.push({type:"v2:room-changed"}),this.roomInfo.length=this._loader.core.length,this.roomInfo.fork=this._loader.core.fork,this.roomInfo.system=this._loader.systemHead,this.isCurrentPrivateProfile&&await this._loadPrivateProfileChanges(a);const o=!!this.roomInfo.selfMember?.status.isRemoved,r=!!this.roomInfo.selfMember?.status.isActive,h=!!this.roomInfo.selfDevice?.status.isActive,c=!!this.roomInfo.selfDevice&&!h,d=o||c&&!!this.currentWriterInfo,u=this.engine.writable&&h&&r,m=!!this.roomInfo.identity;return this.roomInfo.status={isWritable:u,isReadable:!0,isPrivateProfile:m,isRemoved:d},this.roomInfo.timestamps.latestSync=Date.now(),this.roomInfo.timestamps.creation=this.roomInfo.digest.creationTime,this.roomInfo.lastMessage&&(this.roomInfo.timestamps.latestChatMessage=this.roomInfo.lastMessage.timestamp),!0}_shouldUpgrade(){return this.roomInfo.selfMember.status.isModerator&&!(this.roomInfo.digest.abi.version>=this.keet.abi.autoupgrade)||null}_generateMemberUpdate(){const e=this.currentPrivateProfileInfo.selfMember,t=this.roomInfo.selfMember,s=t.profile.timestamp>0,i=e.profile.timestamp<=t.profile.timestamp;if(!e?.profile||s&&i)return null;const a={identity:this.currentIdentityInfo,privateProfile:this.currentPrivateProfileInfo};return{type:"@keet/update-member",op:{member:this.keet.internalGetMemberUpdate(a)}}}_generatePrivateProfileDispatches(){this.updates.profileDispatches=[],this.updates.profileDispatches.push({type:"@keet-private-id/update-room-status",op:{roomKey:this.roomInfo.roomKey,key:h.IsActive,status:{isActive:!1}}})}_generateDispatches(){if(this.updates.dispatches=[],this.updates.shouldUpgrade=this._shouldUpgrade(),!this.currentPrivateProfileInfo?.selfMember)return;const e=this._generateMemberUpdate();e&&this.updates.dispatches.push(e)}_generateTasks(){if(!this.updates.sharedRooms)return;this.updates.tasks=[],this.updates.sharedPreferences=[];const e=this.migratedFromAutobase&&!this.currentRoomInfo?.processed&&!this.updates.attemptRecovery&&!this.updates.attemptIdentitySwap;for(const t of this.updates.sharedRooms){const s=t.mailbox;if(t.identitySwapV2){this.updates.startIdentitySwap={...t.identitySwapV2,privateProfileKey:t.roomKey},this.updates.tasks=[this.keet.tasks.joinRoomKeys(s,{keys:t})];break}e||(t.status.isActive?this.updates.tasks.push(this.keet.tasks.joinRoomKeys(s,{keys:t,activeClock:t.activeClock,isReadOnly:t.status.isReadOnly})):this.updates.tasks.push(this.keet.tasks.leaveRoom(s,{roomKey:t.roomKey})),this.updates.sharedPreferences.push({roomKey:t.roomKey,status:t.status}))}if(!this.updates.startIdentitySwap&&this.updates.pendingInvitations)for(const e of this.updates.pendingInvitations)this.updates.tasks.push(this.keet.tasks.joinRoomInvitation(e.mailbox,{invitation:e}))}async _updateOpenRoomMember(e,t){await e.db.get("@keet/members",{key:t.attestation.memberKey})&&await e.dispatch("@keet/update-member",{member:t})}async _maybeUpdateOpenRooms(e){try{const t=this.currentRoomInfo?.selfMember?.profile.timestamp||0;if(!this.roomInfo?.selfMember||t>=this.roomInfo.selfMember.profile.timestamp)return;const i={profile:this.roomInfo.selfMember.profile,attestation:{memberKey:this.roomInfo.selfMember.key,proof:this.roomInfo.selfMember.proofs.profileAttestation}},a=[];for(const t of this.keet.rooms.values())s.equals(t.key,e)||a.push(this._updateOpenRoomMember(t,i));await Promise.allSettled(a)}catch(e){this.keet.onerror(e)}}async _run(){if(this.closing||this.keet.closing)return Promise.resolve(!1);if(this.opened||await this.ready(),!await this._load()||this.closing)return!1;if(this.roomInfo.status.isWritable&&this._generateDispatches(),this.isCurrentPrivateProfile){const e=this.roomInfo.selfDevice;e&&!e.status.isActive?this.updates.logout=!0:(this._generateTasks(),this._maybeUpdateOpenRooms(this.engine.key))}const e=this.roomInfo.selfMember,t=this.currentRoomInfo?.selfMember;return e?.status.isRemoved&&!t?.status.isRemoved&&this._generatePrivateProfileDispatches(),this._fullyProcessed()&&(this.processed=this._loader.head),!0}_fullyProcessed(){return!this.roomInfo.identity||!!this.roomInfo.selfDevice&&(!this.isCurrentPrivateProfile||!!this.updates.sharedRooms&&!this.updates.startIdentitySwap)}run(){return this._running||(this._running=this._run(),this._running.catch(t)),this._running}};