OverviewFilesCommitsIssuesDocsReleasesPeers

holepunchto-deps / @holepunchto / keet-core / index.js

1 lines · javascript ref:
const e=require("keet-identity-key"),{ProofEncoding:t}=require("keet-identity-key/lib/encoding"),i=require("hypercore-storage"),s=require("hypercore-id-encoding"),o=require("ready-resource"),a=require("ready-guard"),r=require("hyperswarm"),n=require("@holepunchto/call-swarm"),l=require("hyperdht"),c=require("corestore"),h=require("hypercore"),d=require("hyperblobs"),y=require("scope-lock"),u=require("rache"),m=require("protomux-wakeup"),p=require("promaphore"),g=require("safety-catch"),w=require("task-backoff"),f=require("hypercore-crypto"),b=require("z32"),v=require("b4a"),k=require("compact-encoding"),P=require("fs"),K=require("path"),_=require("blind-peering"),C=require("multi-profile-store"),R=require("hyperconf");h.enable(h.SMALL_WANTS),h.setRecoveryPeers&&h.setRecoveryPeers([]);const T=require("@holepunchto/keet-push"),L=require("@holepunchto/keet-unread-anchors"),{generateMnemonic:E}=require("bip39-mnemonic"),{HyperDiscoveryClient:I}=require("@holepunchto/hyperdiscovery"),D=require("./schema/versions"),x=require("./lib/api/v1"),B=require("./lib/api/v2"),S=require("./lib/core/crypto"),A=require("./lib/core/local"),V=require("./lib/core/files"),M=require("./lib/core/tasks"),O=require("./lib/core/sync/processor"),q=require("./lib/core/room-engine"),{SwarmWorker:U}=require("./lib/core/swarm"),N=require("./lib/core/bluetooth"),{getWriterKey:F,getInvitationId:W,createInvitation:H,generateWakeupTopic:j,generateRoomCapabilities:$,generateIdentityPairingName:G,generateRoomPairingName:J}=require("./lib/core/invitations"),{getMemberForDeviceKey:z}=require("./lib/core/contract/query"),{restoreAvatar:Y}=require("./lib/core/contract/avatars"),{RoomStatus:Q}=require("./lib/core/contract/identity/private"),{DEBUG_FLAGS:X,DHT_SETTINGS:Z,SWARM_SETTINGS:ee}=require("./lib/core/debug/debugging"),te=require("./lib/core/log"),{contract:{hyperschema:{getEnum:ie,getEncoding:se,encode:oe,decode:ae}},local:{hyperschema:{getEnum:re}},config:{hyperconf:ne}}=require("./schema/spec"),le=se("@keet-private-id/profile-discovery-record"),ce=ie("@keet/build-type"),he=require("./lib/core/users"),de=require("./lib/core/rooms/index.js"),ye=require("protomux-rpc-client"),{KeetErrorManager:ue,KeetErrorV2:me,ERRORS:pe}=require("./lib/errors"),{Version:ge}=require("bare-semver"),{roomMigrationsStats:we}=require("./lib/core/contract/stats/local"),fe=require("./lib/bundle"),be=require("./lib/core/contract/index.js"),ve=re("@local/keet-state"),ke=2592e6,Pe=v.alloc(32),Ke=require("./lib/env"),{ActiveCall:_e,KeetCallProxy:Ce}=require("./lib/core/call");class Re{constructor(e,t){this.keet=e,this.roomId=t,this.batch=null,this.length=0,this.timestamp=0,this.pushLength=0}get key(){return this.room.engine.local.key}_open(){this.room=this.keet.internalOpenExistingRoom(this.roomId),this.batch=this.room.engine.batch(),this.timestamp=Date.now()}_noop(){return this.close(),this.length}async dispatch(e,t,{dryRun:i=!0}={}){this.room||this._open(),this.room.opened||await this.room.ready();const s=be.encodeOperation(e,{header:{timestamp:this.timestamp},...t});return i&&!await this.keet.internalDryRun(this.room,this.room.view,[s])?this._noop():(this.length=await this.append(s),!this.pushLength&&Ie(e)&&(this.pushLength=this.length),this.length)}async append(e){return this.batch.append(e)}async flush(){try{return await this.batch.flush(),await this.room._postAppend({encoded:this.batch.blocks,pushLength:this.pushLength,timestamp:this.timestamp}),await this.keet.roomProcessor.process(this.room),{key:this.key,length:this.length}}finally{this.close()}}close(){this.room&&this.keet.internalReleaseRoom(this.room),this.batch&&this.batch.close(),this.batch=null,this.length=0,this.shouldPush=!1}}class Te extends o{constructor(e,t,i,o,a,r){super(),this.keet=e,this.id=t,this.key=i,this.engine=r,this.externalBlobs=null,this.discoveryKey=o,this.discoveryId=s.encode(this.discoveryKey),this.view=this.engine.view,this.db=this.view.db,this.core=this.view.core,this.router=this.view.router,this.priority=a,this.push=null,this.keet.push&&(this.push=this.keet.push.room(this.key,this.discoveryKey)),this.refs=0,this.idleTicks=0,this._pushLock=new y({debounce:!0}),this.ready().catch(De)}static create(e,t,i,s,o,a={}){const r=new q(e,t,s,a),{priority:n=1}=a;let l=null;return r.auto.on("interrupt",t=>{e.logger.warn("room=",r.auto.id,"got interrupted"),e.emit("room-interrupted",i,t)}),r.auto.on("warning",t=>{e.logger.warn("[autobee warning] room=",r.auto.id,"error=",t),e.onerror(t,{roomKey:s,prefix:"AUTOBEE_WARNING"})}),r.auto.on("error",t=>{e.logger.warn("[autobee error] room=",r.auto.id,"error=",t),e.onerror(t,{roomKey:s,prefix:"AUTOBEE_ERROR"}),t.code===pe.ROOM_DISABLED&&null!==l&&(e.internalCloseRoom(l),l.close().catch(e.onerror))}),l=new Te(e,i,s,o,n,r),l}get deviceId(){return s.encode(this.deviceKey)}get deviceKey(){return this.engine.local.key}get encryptionKey(){return this.engine.encryptionKey}get blobs(){return this.engine.blobs}getQuickStats(){return{view:this.engine.viewStats,system:this.engine.systemStats,base:this.engine.stats,writer:this.engine.writerStats}}async getStats(){const[e,t,i]=await Promise.all([this.keet.local.get("@local/rooms",{roomKey:this.key}),this.keet.local.get("@local/room-preferences",{roomKey:this.key}),this.keet.local.get("@local/writers-by-room-key",{roomKey:this.key})]);return{...this.getQuickStats(),status:e?.status,preferences:t?.status}}async _createDeviceAttestation(e,t){const i=await this.keet.internalEnterLocalTransaction();try{const s=this.keet.internalAttestData(e,t);return await i.insert("@local/device-attestations",{deviceKey:e,proof:s,createdAt:Date.now()}),await i.flush(),s}finally{await this.keet.internalExitLocalTransaction()}}async _attestedPushNotify(e){const t=this.deviceKey,[i,s]=await Promise.all([this.keet.local.get("@local/identity"),this.keet.local.get("@local/device-attestations",{deviceKey:t})]),o=s?.createdAt||0,a=i.proofShortenedAt||0,r=!s||o<a?await this._createDeviceAttestation(t,i):s.proof;if(this.push&&this.keet.gatekeeperKeys?.length)return void await this.push.send(this.engine.local,e,{version:1,attest:r});const n=this.engine.wakeupCapability&&this.engine.wakeupCapability.key;if(!n)throw new Error("Wakeup capability key must be defined when sending a notification");for(const t of e)await this.keet.blindPeering.sendNotification(this.engine.local,{roomKey:this.engine.key,index:t-1,extra:r,target:n})}async _safePushNotify(e){if(this._pushLengths||(this._pushLengths=[]),this._pushLengths.push(e),!await this._pushLock.lock())return;const t=this._pushLengths;if(this._pushLengths=null,!this.closing&&t){try{await this._attestedPushNotify(t)}catch(e){this.keet.onerror(e,{roomKey:this.key})}this._pushLock.unlock()}else this._pushLock.unlock()}async _safeBumpDiscovery(e){await new Promise(e=>setTimeout(e,100));try{if(this.keet.closing)return;const t=await this.keet.local.get("@local/rooms",{roomKey:this.key});if(!t)return;this.keet.discovery.announce(t.capabilities.discoveryKeyPair,{bump:e})}catch(e){this.keet.onerror(e,{roomKey:this.key})}}async _open(){await this.engine.auto.ready();const e=this.keet.internalIsRoomDisabled(this.key);e||this.internalEnsureMirroring(),await this.engine.ready(),this.engine.auto.on("writer",async e=>{try{if(await this.keet.local.get("@local/devices",{device:{key:e.core.key}}))return;const t=await this.db.get("@keet/devices",{key:e.core.key});t&&await this.db.get("@keet/members",{key:t.memberKey})}catch(e){g(e)}}),this.engine.local.on("remote-contiguous-length",this._handleSentLengthChanged.bind(this)),this.engine.on("rotate-local-writer",()=>{this.engine.local.on("remote-contiguous-length",this._handleSentLengthChanged.bind(this))}),this.db.get("@keet/room-config").then(e=>{this.keet.logger.debug("opened id: "+this.id+" title: "+e?.title)},g),this.externalBlobs=new d(this.keet.store.get({name:this.id+"/blobs",encryptionKey:this.encryptionKey,encrypted:!0,exclusive:!0,active:!1})),await this.externalBlobs.ready(),await this._ensureMetadata(),e||(this.internalEnsureProfileUpdated(),this.internalEnsureUpgraded(),this.internalEnsureAutobeeMigrationShared(),this.internalEnsureAdminDevices(),this.internalEnsureRepaired())}async internalCloseAutobee(){this.keet.internalTime(`internal-close-autobee-${this.id}`),await this.engine.close(),this.keet.internalTimeEnd(`internal-close-autobee-${this.id}`)}async internalCloseExternalBlobs(){this.keet.internalTime(`internal-close-blobs-${this.id}`),await this.externalBlobs.close(),this.keet.internalTimeEnd(`internal-close-blobs-${this.id}`)}async internalPushDestroy(){this.push&&(this.keet.internalTime(`internal-close-push-${this.id}`),await this.push.destroy(),this.keet.internalTimeEnd(`internal-close-push-${this.id}`))}_close(){return Promise.all([this.internalCloseAutobee(),this.internalCloseExternalBlobs(),this.internalPushDestroy()])}_handleSentLengthChanged(){if(!this.engine.writable)return;const{length:e,remoteContiguousLength:t}=this.engine.local;e===t&&this.keet.emit("room-sent-message",this.engine.id)}async _ensureMetadata(){const e=await this.keet.internalEnterLocalTransaction();try{const[t,i]=await Promise.all([e.get("@local/room-metadata",{roomKey:this.key}),e.get("@local/rooms",{roomKey:this.key})]);if(t)return;const s=i?0:1;await e.insert("@local/room-metadata",{roomKey:this.key,repairs:s}),await e.flush()}finally{await this.keet.internalExitLocalTransaction()}}async internalEnsureMirroring(){this.refs++;try{const e=this.engine.wakeupCapability||await this.keet.internalGetMailbox(this.key),t=e&&e.key;if(!t)return;const i=await this.keet._getMirrorKeys(this.key);if(this.closing)return;this.keet.blindPeering.addAutobaseBackground(this.engine.auto,{target:t,keys:i,priority:this.priority});try{await this.engine.ready()}catch{return}if(this.closing)return;for(const e of this.engine.migratedViews)this.keet.blindPeering.addCoreBackground(e,{target:t,keys:i,priority:this.priority})}finally{this.refs--}}async internalIsMirroringView(){const e=this.engine.viewCore;if(!e||e.remoteContiguousLength===e.length)return!1;const t=await this.keet.local.get("@local/rooms",{roomKey:this.key});return!!t?.selfMember?.status.isAdmin&&(await this.keet._getMirrorKeys(this.key,t)).length>0}async internalEnsureUpgraded({opening:e=!1}={}){this.refs++;try{e||this.keet.onlineBoot.opened||await this.keet.onlineBoot.ready();const[t,i]=await Promise.all([this.db.get("@keet/system-digest"),z(this.db,this.deviceKey)]);if(!i?.status.isModerator||t?.abi.version>=this.keet.abi.autoupgrade)return;await this.keet.internalDispatchUpgrade(this.id,this.keet.abi.autoupgrade)}catch(e){this.keet.onerror(e,{roomKey:this.key})}finally{this.refs--}}async internalEnsureProfileUpdated(){this.refs++;try{this.keet.onlineBoot.opened||await this.keet.onlineBoot.ready();const[e,t]=await Promise.all([this.keet.local.get("@local/identity"),z(this.db,this.deviceKey)]);if(this.closing||!e||!t||v.equals(this.key,e.privateProfileKey))return;const i=await this.keet.local.get("@local/rooms",{roomKey:e.privateProfileKey});if(this.closing)return;if(t.profile.timestamp>=i.selfMember.profile.timestamp)return;const s={profile:i.selfMember.profile,attestation:{memberKey:i.selfMember.key,proof:i.selfMember.proofs.profileAttestation}};await this.dispatch("@keet/update-member",{member:s})}catch(e){this.keet.onerror(e,{roomKey:this.key})}finally{this.refs--}}async internalEnsureAdminDevices(){this.refs++;try{this.keet.onlineBoot.opened||await this.keet.onlineBoot.ready();const e=await this.keet.local.get("@local/rooms",{roomKey:this.key});if(this.closing||!e)return;const t=D.FEATURES.ADMIN_DEVICE_INDEXING;if(!D.isFeatureSupported(e.digest.abi.version,t))return;if(!e.selfMember||!e.selfMember.status.isAdmin)return;if(!e.selfDevice||e.selfDevice.status.isIndexing)return;await this.dispatch("@keet/claim-admin-devices",{})}catch(e){this.keet.onerror(e,{roomKey:this.key})}finally{this.refs--}}async internalEnsureAutobeeMigrationShared(){this.refs++;try{if(this.keet.onlineBoot.opened||await this.keet.onlineBoot.ready(),this.closing)return;const e=this.key,t=await this.keet.local.get("@local/rooms",{roomKey:e});if(!t||!t.status.isWritable)return;if(!t.selfMember||!t.selfMember.status.isAdmin)return;const i=await this.db.get("@keet/autobee-migration");if(this.closing||i)return;const s=await this.keet.local.get("@local/autobee-migrations",{roomKey:e});if(!s)return;const o={head:s.autobeeMigrationHead};s.legacyViewHead&&(o.legacyViewHead=s.legacyViewHead),s.dbViewHead&&(o.dbViewHead=s.dbViewHead),s.blobsViewHead&&(o.blobsViewHead=s.blobsViewHead),await this.dispatch("@keet/autobee-migration",o)}catch(e){this.keet.onerror(e,{roomKey:this.key})}finally{this.refs--}}async internalGetBootFrom(){const[e,t]=await Promise.all([this.keet.local.get("@local/autobee-migrations",{roomKey:this.key}),this.db.get("@keet-private-id/identity")]);if(e&&!await this.db.get("@keet/autobee-migration")){const t=e.autobeeMigrationHead;return t?{key:t.key,length:0}:null}const i=this.engine.auto.local;return t?{key:i.key,length:i.length+1}:this.engine.getMostRecentAdmin()}async internalEnsureRepaired(){this.refs++;try{if(this.keet.onlineBoot.opened||await this.keet.onlineBoot.ready(),this.closing||this.keet.closing)return;if(this===this.keet.privateProfile)return;const e=await this.keet.local.get("@local/room-metadata",{roomKey:this.key});if(this.closing||this.keet.closing||e.repairs>=1)return;if(!this.engine.shouldRepair())return;const t=await this.db.get("@keet-private-id/identity");if(this.closing||this.keet.closing||t)return;if(e.repairs=1,await this.engine.repair(),this.closing||this.keet.closing)return;const i=await this.keet.internalEnterLocalTransaction();try{await i.insert("@local/room-metadata",e),await i.flush()}finally{await this.keet.internalExitLocalTransaction()}}finally{this.refs--}}_encodeBatch(e){const t=Array.isArray(e)?e:[e],i=Date.now(),s=[];let o=!1;for(const{type:e,op:a}of t)s.push(be.encodeOperation(e,{header:{timestamp:i},...a})),Ie(e)&&(o=!0);return{encoded:s,timestamp:i,shouldPush:o}}async _postAppend({encoded:e=[],pushLength:t=0,timestamp:i=Date.now()}){this.keet.stats.ops+=e.length,(this.push||this.keet.blindPeering?.keys.length)&&t>0&&this._safePushNotify(t),this.keet.discovery&&this._safeBumpDiscovery(i),this.db.update()}async dispatchBatch(e,{view:t=this.view,dryRun:i=!0,optimistic:s=!1}={}){this.opened||await this.ready();const{encoded:o,timestamp:a,shouldPush:r}=await this._encodeBatch(e,{view:t});if(i&&!await this.keet.internalDryRun(this,t,o))return null;await this.engine.append(o,{optimistic:s});const n=this.engine.local.length,l=r?n:0;return await this._postAppend({encoded:o,pushLength:l,timestamp:a}),await this.keet.roomProcessor.process(this),{key:this.engine.local.key,length:n}}dispatch(e,t,i){return this.dispatchBatch([{type:e,op:t}],i)}}function Le(t,i,s){const o=e.attestDevice(s,t.deviceKeyPair,t.deviceProof),a=e.attestData(i,t.deviceKeyPair,t.deviceProof);return{privateProfileIdentity:{memberKey:t.publicKey,proof:a},deviceProof:o}}function Ee(e){return e.map(({mediaStreamId:e,type:t,muted:i})=>({streamKey:f.randomBytes(32),mediaStreamId:e,type:t,muted:i}))}function Ie(e){return"@keet/add-chat-message"===e||"@keet/start-call"===e||"@keet/join-call"===e||"@keet/leave-call"===e||"@keet/end-call"===e||"@keet/update-read-length"===e}function De(){}module.exports=class extends o{constructor(e,t={}){if(super(),this.dir=e,this.opts=t,this.isMobile=!!t.mobile,this.isTesting=!!t.testing,this.appVersion=t.appVersion?ge.parse(t.appVersion):null,this.forceBuildType=!!t.forceBuildType,!t.buildType)throw me.INVALID_BUILD_TYPE();this.buildType=function(e){switch(e){case"nightly":return ce.Nightly;case"internal":return ce.Internal;case"production":return ce.Production;default:throw me.INVALID_BUILD_TYPE()}}(t.buildType),this.logger=new te(t.logger),this.isTesting&&this.logger.mute(),this.callHeartbeatOpts=t.callHeartbeat||{},this.callProxy=new Ce;const o=Ke(t.buildType);this.isNightly=this.buildType===ce.Nightly,this.isExperimental=this.isTesting?t.experimental??this.isNightly:this.isNightly,this.offlineBoot=new a,this.onlineBoot=new a,this.debug={},this.versions=D,this.abi=t.testingABI||{max:this.isExperimental?D.EXPERIMENTAL:D.STABLE,autoupgrade:this.isExperimental?D.EXPERIMENTAL:D.AUTOUPGRADE,production:D.STABLE},this.abi.min=D.MINIMUM,this.logout=t.logout,this.storage=new i(e,t.storage),this.rootCorestore=new c(this.storage,{globalCache:new u({maxSize:t.maxCacheSize}),manifestVersion:2}),this.store=this.rootCorestore.namespace("keet-core"),this.rootCorestore.watch(this._handleCoreOpen.bind(this)),this.local=A.rocks(this.storage.rocks.columnFamily("keet/local-database")),this.discovery=null,this.gatekeepers=t.gatekeepers||[],this.push=null,this.protomuxWakeup=new m(this._handleWakeup.bind(this)),this.backoff=new w,this.rooms=new Map,this.activeRooms=new Set,this._transactionLock=new y,this._tx=null,this.hyperconf=null,this.config=null,this.configMinVersion=null,this.configDesktopMinVersion=null,this.errorManager=new ue(this,this.opts),this.swarmWorker=new U(this,this.opts.swarmWorker),this.files=new V(this,this.opts),this._onerror=t.onerror||g,this.onerror=this._handleError.bind(this),this.apiV1=new x(this,t),this.backendAPI=this.apiV1,this.api=this.backendAPI.api.core,this.blindPeeringOpts=t.blindPeeringOpts||{},this.contractBundles=null,this.roomProcessor=null,this.tasks=null,this.swarm=null,this.swarmId=null,this.blindPeering=null,this.privateProfile=null,this.unreads=null,this.unreadsLoading=null,this.userRegistry=null,this.roomRegistry=null,this.discoveryCore=null,this.discoveryCoreSession=null,this.push=null,this.state=ve.RESUME,this._bundlesKey=t.bundlesKey||o.bundles.key,this._bundlesLength=t.bundlesLength||o.bundles.length,this._hyperconfKey=t.hyperconfKey||o.config.key,this._hyperconfLength=t.hyperconfLength||o.config.length,this._roomRegistryOpts=t.roomRegistry||o.roomRegistry,this._disabledRooms=this.isTesting?new Set((t.disabledRooms||[]).map(e=>s.normalize(e))):null,this._verifiedRooms=this.isTesting?new Set:null,this._relayKeys=this.isTesting?t.blindRelays||[]:null,this._blindPeerKeys=this.isTesting?t.blindMirroring||[]:null,this._gatekeeperKeys=this.isTesting?t.gatekeepers||[]:null,this._discoveryBootstrapKeys=this.isTesting?t.discoveryBootstrapKeys||[]:null,this._disableUnreadSync=this.isTesting&&t.disableUnreadSync||!1,this._otaResyncPrivateProfileBump=this.isTesting&&t.otaResyncPrivateProfileBump||0,this._migratedFrom=0,this._unreadsLock=new y({debounce:!0}),this._dhtOpts=t.dht,this._swarmOpts=t.swarm,this._bluetoothOpts=t.bluetooth,this._pendingTasks=[],this._swarmTasks=[],this._runningTasks=new Set,this._waitingForCompletion=new Map,this._completedRequests=new Set,this._pendingCandidates=new Map,this._swarming=new Map,this._pendingTraces=[],this._cacheLock=new y({debounce:!0}),this._gcEnabled=!1!==t.gc,this._gcIdleTicks=t.gcIdleTicks||4,this._gcInterval=t.gcInterval||5e3,this._gcTimer=null,this._gcClosingRooms=new Set,this._boundAutobeeTrace=this._autobeeTrace.bind(this),this.internalPairingTimeout=t.pairingTimeout||3e3,this._profileDiscoveryTimeout=t.profileDiscoveryTimeout||1e4,this.migrationStatus=null,this._roomMigrations=new p(5),this._discoveryLock=new y({debounce:!0}),this._discoverySemaphore=new p(10),this._discovering=new Map,this.internalFastJoinSemaphore=new p(4),this.debugRunningFastJoins=0,this.debugMaxRunningFastJoins=0,this._rejectAnonymousProfiles=!1!==t.rejectAnonymousProfiles,this._registryOpts=this.isTesting?t.userRegistry:null,this.rpcClient=null,this.ackThreshold=t.ackThreshold,this.ackInterval=t.ackInterval,this.stats={ops:0,appends:0,bytesAppended:0,uploaded:0,bytesUploaded:0,downloaded:0,bytesDownloaded:0},this.debugForceMigrationFailure=t.debugForceMigrationFailure||null,this.debugJoinPeer=t.debugJoinPeer||this._debugJoinPeer.bind(this),this.debugLeavePeer=t.debugLeavePeer||this._debugLeavePeer.bind(this),this.debugDisableCacheBump=!1,this.debugClosing=!1,this.debugFailRoomSync=t.debugFailRoomSync||null,this.debugFailNextRoomSync=t.debugFailNextRoomSync||null,this.debugDisableSyncing=!0===t.debugDisableSyncing,this.ready().catch(this.onerror)}async backendReady(){this.backendAPI.opened||await this.backendAPI.ready()}get readOnly(){return this.rootCorestore.storage.readOnly||!1}get suspended(){return this.state===ve.SUSPEND}async internalEnableBluetooth(){"unsupported"!==this.bluetooth.state&&await this.bluetooth.setEnabled(!0)}async internalDisableBluetooth(){"unsupported"!==this.bluetooth.state&&await this.bluetooth.setEnabled(!1)}get gatekeeperKeys(){return this._gatekeeperKeys}async _openNetworking(){const e=await this.getDHTSettings(),t=await this.getSwarmSettings();this.swarm=new r({deferRandomPunch:!0,dht:new l({...e,...this._swarmOpts,...this._dhtOpts}),maxPeers:t.maxPeers,...this._swarmOpts,relayThrough:this._relayThrough.bind(this),keyPair:await this.rootCorestore.createKeyPair("keet-swarm-keypair")}),this.swarmId=s.encode(this.swarm.keyPair.publicKey),this.swarm.on("connection",this._handleConnection.bind(this)),this.bluetooth=new N(this,this._bluetoothOpts),this.callSwarmKeyPair=await this.rootCorestore.createKeyPair("keet-call-swarm-keypair"),this.activeCall=null,this.closingCallSwarms=new Set,this.discovery=this._discoveryBootstrapKeys.length?new I(this._discoveryBootstrapKeys[0],{dht:this.swarm.dht}):null,this.rpcClient=new ye(this.swarm.dht,{suspended:this.state===ve.SUSPEND,relayThrough:this._relayThrough.bind(this)}),this.blindPeering=new _(this.swarm.dht,this.store,{pick:2,wakeup:this.protomuxWakeup,keys:this.debug.disableMirrors?[]:this._blindPeerKeys,relayThrough:this._relayThrough.bind(this),...this.blindPeeringOpts}),this._gatekeeperKeys.length>0&&(this.push=new T(this._gatekeeperKeys,{dht:this.swarm.dht})),await this.swarmWorker.ready(),await this.files.ready(),this.readOnly&&(this.blindPeering.suspend(),this.swarm.suspend())}async _runBootOffline(){if(!this.offlineBoot.enter())return;await this._ensureDebugState(),await this.store.ready(),this.hyperconf=new R(ne,this.store.get(this._hyperconfKey),{minLength:this._hyperconfLength,production:this.buildType===ce.Production}),this.hyperconf.on("update",this._handleConfigUpdated.bind(this)),await this.hyperconf.ready(),await this._handleConfigUpdated(),this.config?.versions.autoupgrade>0&&(this.abi.autoupgrade=this.config.versions.autoupgrade);const e=await this.local.get("@local/environment"),t=!!e&&e.latestOfflineAbi<this.abi.production;this.contractBundles=new fe(this,"keet-bundles",{hydrate:t}),await this.contractBundles.ready(),t&&await this._bumpLocalAbiVersion(),this.roomProcessor=new O(this),await this.roomProcessor.ready(),await this._openNetworking(),this.tasks=new M(this),await this.tasks.ready(),this.roomProcessor.bump(),this._gcEnabled&&(this._gcTimer=setInterval(this._closeIdleRooms.bind(this),this._gcInterval)),this.offlineBoot.exit()}async _runBootOnline(){if(this.onlineBoot.enter()){await this.offlineBoot.ready(),this.discovery&&(this.discovery.on("announce",({publicKey:e})=>{this.scheduleDiscoverySwarming(e)}),this.discovery.on("connect",this._onDiscoveryConnect.bind(this)),this.discovery.connect()),this._refreshDiscoverySwarming(),this._migratedFrom=await this._ensureMigrated();try{await this._loadIdentity()}catch(e){this.onerror(e)}this._migratedFrom>0&&await this._maybeRunPostMigrations(this._migratedFrom),this._registryOpts&&(this.userRegistry=new he(this,this._registryOpts),await this.userRegistry.ready()),this._roomRegistryOpts&&(this.roomRegistry=new de(this,this._roomRegistryOpts),await this.roomRegistry.ready()),this.onlineBoot.exit(),this._resyncPrivateProfileOnBoot().catch(this.onerror)}}async _open(){!1!==await this._ensureBuildType()&&(this._runBootOffline().catch(this.onerror),this._runBootOnline().catch(this.onerror),await this.offlineBoot.ready())}async _close(){this.internalTime("keet-close"),this.internalVerboseLog("Closing keet core"),this._gcTimer&&clearInterval(this._gcTimer),this.backoff.destroy(),this.internalTimeLog("keet-close","destroyed backoff"),this.state===ve.SUSPEND&&await this.store.resume(),this.internalTimeLog("keet-close","resumed from suspend"),this.internalVerboseLog("Closing active calls"),await this.backendAPI.closeActiveCalls(),this.internalTimeLog("keet-close","closed active calls"),this.internalVerboseLog("Closing discovery cores"),this.discoveryCore&&await this.discoveryCore.close(),this.discoveryCoreSession&&this.discoveryCoreSession.destroy(),this.internalTimeLog("keet-close","closed discovery cores"),this.internalVerboseLog("Closing tasks"),this.tasks&&await this.tasks.close(),this.internalFastJoinSemaphore.destroy(),this.internalTimeLog("keet-close","stopped running tasks");for(const e of this._gcClosingRooms)this.internalTime(`gc-room-close-${e.id}`),this.internalTimeLog(`gc-room-close-${e.id}`),this.internalVerboseLog("Closing room in gc list",e.id,e.opened),await e.close(),this.internalTimeEnd(`gc-room-close-${e.id}`,"closed");this.internalTimeLog("keet-close","closed rooms in gc"),this.internalVerboseLog("Closing user registry"),this.userRegistry&&await this.userRegistry.close(),this.internalTimeLog("keet-close","closed user registry"),this.internalVerboseLog("Closing room registry"),this.roomRegistry&&await this.roomRegistry.close(),this.internalTimeLog("keet-close","closed room registry"),this.internalVerboseLog("Closing rpc client"),this.rpcClient&&await this.rpcClient.close(),this.internalTimeLog("keet-close","closed rpc client"),this.internalVerboseLog("Closing swarm worker"),await this.swarmWorker.close(),this.internalTimeLog("keet-close","closed swarm worker"),this.internalVerboseLog("Closing discovery"),this.discovery&&await this.discovery.close(),this.internalTimeLog("keet-close","closed discovery"),this.internalVerboseLog("Closing push"),this.push&&await this.push.destroy(),this.internalTimeLog("keet-close","closed push"),this.internalVerboseLog("Closing blind peering"),this.blindPeering&&await this.blindPeering.close(),this.internalTimeLog("keet-close","closed blind peering"),this.internalVerboseLog("Flushing DHT cache"),this.swarm&&await this._flushDHTCache(),this.internalTimeLog("keet-close","flushed dht"),this.internalVerboseLog("Closing active call"),this.activeCall&&await this.internalEndActiveCall(),this.internalTimeLog("keet-close","closed active call"),this.internalVerboseLog("Closing swarm"),this.swarm&&await this.swarm.destroy(),this.internalTimeLog("keet-close","destroyed swarm"),this.internalVerboseLog("Closing files"),await this.files.close(),this.internalTimeLog("keet-close","closed files"),this.internalVerboseLog("Closing all pending rooms and workers"),await Promise.allSettled([...[...this.rooms.values()].map(async e=>{this.internalTime(`pending-room-close-${e.id}`),this.internalTimeLog(`pending-room-close-${e.id}`),await e.close(),this.internalTimeEnd(`pending-room-close-${e.id}`)})]),this.internalTimeLog("keet-close","closed all pending rooms and workers"),this.internalVerboseLog("Closing room processor"),this.roomProcessor&&await this.roomProcessor.close(),this.internalTimeLog("keet-close","closed room processor"),this.internalVerboseLog("Closing backend"),await this.apiV1.close(),this.internalTimeLog("keet-close","closed backend"),await this.internalEnterLocalTransaction(),await this.internalExitLocalTransaction(),this.internalVerboseLog("Closing unread anchors"),this.unreadsLoading&&await this.unreadsLoading,this.unreads&&await this.unreads.close(),this.internalTimeLog("keet-close","closed unreads"),this.internalVerboseLog("Closing bluetooth"),this.bluetooth&&await this.bluetooth.close(),this.internalTimeLog("keet-close","bluetooth"),this.internalVerboseLog("Closing wakeup"),this.protomuxWakeup.destroy(),this.internalTimeLog("keet-close","protomux"),this.internalVerboseLog("Closing hyperconf"),this.hyperconf&&await this.hyperconf.close(),this.internalVerboseLog("Closing error manager"),this.errorManager&&await this.errorManager.close(),this.internalVerboseLog("Closing local db"),await this.local.close(),this.internalVerboseLog("Closing contract bundles"),this.contractBundles&&await this.contractBundles.close(),this.internalVerboseLog("Closing store"),await this.store.close(),this.internalVerboseLog("Closing root corestore"),await this.rootCorestore.close(),this.internalTimeLog("keet-close","closed cores"),this.internalVerboseLog("Close done"),this.internalTimeEnd("keet-close")}async _ensureDebugState(){const e=await this.internalEnterLocalTransaction();try{const t=await e.get("@local/debug")||{};t.flags||(t.flags=X);const i=new Set,s={};for(const e of t.flags)i.add(e.key),s[e.key]=e.value;for(const{key:e,value:s,description:o}of X)i.has(e)||t.flags.push({key:e,value:s,description:o});await e.insert("@local/debug",t),await e.flush(),this.debug={...s}}finally{await this.internalExitLocalTransaction()}}isValidBuildType(e){if(!e)return!0;if(this.isTesting&&!this.forceBuildType)return!0;const t=ce.Production===this.buildType;return!(!t&&!e.settings.buildType||!t&&e.settings.buildType!==this.buildType)}async _ensureBuildType(){if(this.isTesting&&!this.forceBuildType)return;const e=ce.Production===this.buildType,t=await this.internalEnterLocalTransaction();try{const[i,s]=await Promise.all([t.get("@local/identity"),t.get("@local/environment")]);if(!e&&i&&!s?.buildType)throw me.INVALID_BUILD_TYPE();if(s&&s.buildType!==this.buildType)throw me.INVALID_BUILD_TYPE();if(s)return;await t.insert("@local/environment",{buildType:this.buildType}),await t.flush()}catch(e){return this.onerror(e),!1}finally{await this.internalExitLocalTransaction()}}async _bumpLocalAbiVersion(){const e=await this.internalEnterLocalTransaction();try{const t=await e.get("@local/environment");t.latestOfflineAbi=this.abi.production,await e.insert("@local/environment",t),await e.flush()}finally{await this.internalExitLocalTransaction()}}async _onDiscoveryConnect(){const e=await this.local.find("@local/rooms-by-timestamp",{reverse:!0,limit:256}).toArray();this.discovery.cork();for(const t of e){const e=t.capabilities.discoveryKeyPair.publicKey,i=await this.local.get("@local/background-sync",{discoveryPublicKey:e}),s=Math.max(t.timestamps.latestSync,i?i.lastSynced:0);this.discovery.subscribe(t.capabilities.discoveryKeyPair.publicKey,{since:s})}this.discovery.uncork()}async internalEnterLocalTransaction(){return await this._transactionLock.lock(),this._tx=this.local.transaction(),this._tx}async internalExitLocalTransaction(){await this._tx.close(),this._transactionLock.unlock()}async _handleConfigUpdated(){if(!this.isTesting||this.opts.hyperconfKey){if(this.config=this.hyperconf.current,this.internalVerboseLog("OTA config updated, dkey:",s.encode(this.hyperconf.core.discoveryKey),"length:",this.hyperconf.core.length),this._blindPeerKeys=this.config.services.blindPeers,this._relayKeys=this.config.services.blindRelays,this._gatekeeperKeys=this.config.services.gatekeepers,this._discoveryBootstrapKeys=this.config.services.discovery,h.setRecoveryPeers&&h.setRecoveryPeers(this.config.services.blindPeers),this._disabledRooms=new Set(this.config.rooms.disabled.map(e=>s.encode(e))),this._verifiedRooms=new Set(this.config.rooms.verified?.map(e=>s.encode(e))),this._disableUnreadSync=!0===this.config.profile?.disableUnreadSync,this._otaResyncPrivateProfileBump=this.config.profile?.otaResyncPrivateProfileBump||0,this._maybeOtaResyncPrivateProfile().catch(this.onerror),this._maybeUpgradeOpenRooms().catch(this.onerror),this._closeDisabledRooms().catch(this.onerror),this.config.services.userRegistries?.length&&(this._registryOpts=this.config.services.userRegistries[0]),this.blindPeering&&this.blindPeering.setKeys(this._blindPeerKeys),this.config.versions.minVersion){const{major:e,minor:t,patch:i}=this.config.versions.minVersion;this.configMinVersion=new ge(e,t,i)}if(this.config.versions.desktopMinVersion){const{major:e,minor:t,patch:i}=this.config.versions.desktopMinVersion;this.configDesktopMinVersion=new ge(e,t,i)}this.config.versions.autoupgrade>0&&(this.abi.autoupgrade=this.config.versions.autoupgrade),this.emit("force-update"),this.emit("verified-rooms-changed")}}async _maybeUpgradeOpenRooms({debug:e=!1}={}){this.onlineBoot.opened||await this.onlineBoot.ready();const t=this.abi.autoupgrade,i=this.config?.versions.autoupgrade;if(!(t<i)&&!0!==e)return;const s=[],o=async function(e,t){try{await e.ready();const[i,s]=await Promise.all([e.db.get("@keet/system-digest"),z(e.db,e.deviceKey)]);if(!s?.status.isModerator||i?.abi.version>=t)return;await this.internalDispatchUpgrade(e.id,t)}catch(e){this.onerror(e)}}.bind(this);for(const[e,t]of this.rooms.entries())s.push(o(t,i));return await Promise.all(s)}async _maybeOtaResyncPrivateProfile(){this.onlineBoot.opened||await this.onlineBoot.ready();const e=this._otaResyncPrivateProfileBump||0;try{if(e<=((await this.local.get("@local/ota-actions"))?.otaResyncPrivateProfileBump||0))return;if(null===await this.internalResyncPrivateProfile())return;const t=await this.internalEnterLocalTransaction();try{await t.insert("@local/ota-actions",{otaResyncPrivateProfileBump:e}),await t.flush()}finally{await this.internalExitLocalTransaction()}}catch(e){this.onerror(e)}}shouldForceUpdate(){return this.config.versions.forceUpdate>D.FORCE_UPDATE||(this.isMobile?this._shouldForceUpdateMobile():this._shouldForceUpdateDesktop())}_shouldForceUpdateMobile(){return!(!this.configMinVersion||!this.appVersion)&&1===this.configMinVersion.compare(this.appVersion)}_shouldForceUpdateDesktop(){return!(!this.configDesktopMinVersion||!this.appVersion)&&1===this.configDesktopMinVersion.compare(this.appVersion)}isRoomVerified(e){return this._verifiedRooms.has(e)}internalIsRoomDisabled(e){return!!this._disabledRooms&&this._disabledRooms.has(s.encode(e))}async _bumpRoom(e){if(this.closing)return;if(!await this.local.get("@local/writers-by-room-key",{roomKey:e}))return;const t=await this.local.get("@local/rooms",{roomKey:e});if(!t||t.status.isRemoved)return;if(this.closing)return;const i=this.internalOpenExistingRoom(e);try{await i.ready()}finally{this.internalReleaseRoom(i)}}async _roomCoreReferrer(e){const t=e.state.storage.read(),i=[t.getUserData("referrer"),t.getUserData("autobase/view")];t.tryFlush();const[s,o]=await Promise.all(i);return s&&!o?s:null}async _tagLegacyCore(e){if(await e.ready(),e.core.header.group)return;if(e.writable)return;const t=await this._roomCoreReferrer(e.core);if(!t)return;const i=await this.internalGetMailbox(t);i&&(await e.setGroup(i.discoveryKey),await this._bumpRoom(t))}_handleCoreOpen(e){const t=new h({core:e,weak:!0}),i=new y({debounce:!0});this._tagLegacyCore(t).catch(this.onerror),t.on("verification-error",e=>{this.onerror(e,{coreKey:t.key,prefix:"CORE_VERIFICATION_ERROR"})}),t.on("conflict",()=>{this.onerror(new Error("Conflict in Hypercore"),{coreKey:t.key,prefix:"CORE_CONFLICT"})}),t.on("invalid-request",e=>{this.onerror(e,{coreKey:t.key,prefix:"CORE_INVALID_REQUEST"})}),t.on("append",async()=>{if(await i.lock())try{const t=await this._roomCoreReferrer(e);if(!t)return;await this._bumpRoom(t)}catch(e){this.onerror(e)}finally{i.unlock()}}),t.on("upload",(e,t)=>{this.stats.uploaded++,this.stats.bytesUploaded+=t}),t.on("download",(e,t)=>{this.stats.downloaded++,this.stats.bytesDownloaded+=t}),t.once("ready",()=>{if(t.writable){let e=t.length,i=t.byteLength;t.on("append",()=>{const s=t.length,o=t.byteLength;this.stats.appends+=s-e,this.stats.bytesAppended+=o-i,e=s,i=o})}})}async _handleWakeup(e,t){if(!this.discovery||this._isBluetoothConnection(t.stream))try{const[t,i]=await Promise.all([this.local.get("@local/writers-by-wakeup-topic",{wakeupTopic:e}),this.local.get("@local/rooms-by-wakeup-topic",{capabilities:{wakeupTopic:e}})]);if(!t&&!i)return;const s=t?.mailbox||i?.capabilities.mailboxKeyPair.publicKey,o=this.protomuxWakeup.session(s,{active:!1,discoveryKey:e,onpeeractive:e=>{t&&o.announce(e,[{key:t.key,length:1}])}});i&&await this._bumpRoom(i.roomKey)}catch(e){this.onerror(e)}}_isBluetoothConnection(e){return!(!this.bluetooth||!this.bluetooth.isEnabled)&&this.bluetooth.swarm.connections.has(e)}async _maybeRunMigrations(e,t){3===t&&await require("./lib/core/migrations/3")(this,e),4===t&&await require("./lib/core/migrations/4")(this,e),5===t&&await require("./lib/core/migrations/4")(this,e),t<8&&await require("./lib/core/migrations/7")(e),t<9&&await require("./lib/core/migrations/8")(e)}async _maybeRunPostMigrations(e){4===e&&await require("./lib/core/migrations/4/post")(this),5===e&&await require("./lib/core/migrations/4/post")(this)}async _ensureMigrated(){let e=0;const t=await this.internalEnterLocalTransaction();try{const i=await t.get("@local/migrations"),s=i?i.version:3;s<9&&(e=s,await this._maybeRunMigrations(t,s),await t.insert("@local/migrations",{version:9})),await t.flush()}finally{await this.internalExitLocalTransaction()}const i=await this.local.find("@local/room-migrations-by-priority").toArray(),o=[],a=[],r=[];for(const e of i)this._disabledRooms.has(s.encode(e.roomKey))?r.push(e):e.v3?.privateProfile?o.push(e):a.push(e);if(r.length>0){const e=await this.internalEnterLocalTransaction();try{for(const t of r){const i=await e.get("@local/room-migrations",{roomKey:t.roomKey});await e.delete("@local/room-migrations",t),await we(e,null,i)}await e.flush()}finally{await this.internalExitLocalTransaction()}}await Promise.all(o.map(e=>this._migrateRoom(e,{migratingRooms:a})));for(const e of a)this._migrateRoom(e).catch(this.onerror);return e}async _migrateRoom(e,{migratingRooms:t}={}){await this._roomMigrations.wait();try{const i=e.errored;if(i||(this.migrationStatus||(this.migrationStatus={remaining:0}),this.migrationStatus.remaining++,this.emit("migration-status",this.migrationStatus)),!e.timestamp){const t=await this.internalEnterLocalTransaction();try{e.timestamp=Date.now();const i=await t.get("@local/room-migrations",e);await t.insert("@local/room-migrations",e),await we(t,e,i),await t.flush()}finally{await this.internalExitLocalTransaction()}}const s=Date.now()-e.timestamp>1728e5;let o=!1,a=!1;try{e.v3&&!s&&await require("./lib/core/migrations/3/room")(this,e.roomKey,e.v3,{migratingRooms:t})}catch(e){this.onerror(e),a=!0,o=!!this.closing}const r=await this.internalEnterLocalTransaction();try{const t=await r.get("@local/room-migrations",e);a?(i||(e.errored=!o),await r.insert("@local/room-migrations",e),await we(r,e,t)):(await r.delete("@local/room-migrations",e),await we(r,null,t)),await r.flush()}finally{await this.internalExitLocalTransaction()}i||(this.migrationStatus.remaining--,this.emit("migration-status",this.migrationStatus))}finally{this._roomMigrations.signal()}}async _closeIdleRooms(){const e=[],t=[];for(const i of this.rooms.values())i.refs>0||(i.engine.busy?i.idleTicks=0:this.activeRooms.has(i)||++i.idleTicks<this._gcIdleTicks||i.refs>0||this.rooms.get(i.id)!==i||(this.rooms.delete(i.id),this._gcClosingRooms.add(i),e.push(i.close()),t.push(i)));const i=await Promise.allSettled(e);for(let e=0;e<i.length;e++)"rejected"===i[e].status&&this.onerror(i[e].reason,{roomKey:t[e].key,prefix:"ROOM_GC_CLOSE"});for(const e of t)this._gcClosingRooms.delete(e)}async _closeDisabledRooms(){for(const e of[...this.rooms.values()])this._disabledRooms.has(e.id)&&(this.internalCloseRoom(e),await e.close())}internalCloseRoom(e){this.rooms.delete(e.id),this._gcClosingRooms.add(e)}_debugJoinPeer(e){this.logger.info("Call join:",e)}_debugLeavePeer(e){this.logger.info("Call leave:",e)}async debugPauseCacheBump(){this.debugDisableCacheBump=!0,await this._cacheLock.flush()}debugResumeCacheBump(){this.debugDisableCacheBump=!1}debugPauseGC(){this._gcTimer&&(clearInterval(this._gcTimer),this._gcTimer=null)}debugResumeGC(){this._gcTimer||(this._gcTimer=setInterval(this._closeIdleRooms.bind(this),this._gcInterval))}debugMaybeUpgradeOpenRooms(e={}){return this._maybeUpgradeOpenRooms({debug:!0,...e})}async internalDiscoveryCompleted(e,t){try{const e=this._discovering.get(t);if(!e)return;const i=await this.internalEnterLocalTransaction();try{const t=e.roomInfo.capabilities.discoveryKeyPair.publicKey;e.room&&this.internalReleaseRoom(e.room);const s=await i.get("@local/background-sync",{discoveryPublicKey:t});s&&await i.insert("@local/background-sync",{...s,lastSynced:Date.now(),dirty:!1}),await i.flush()}finally{await this.internalExitLocalTransaction()}this._discovering.delete(t),this._discoverySemaphore.signal()}catch(e){this.onerror(e)}}async _startDiscoverySwarming(e,t){if(!await this._discoverySemaphore.wait())return!1;if(this.closing)return!1;const i=this._discovering.get(t);return!!i&&(this.swarmWorker.startDiscoverySwarming(e,{roomId:t}),i.updated&&this.swarmWorker.notifyDiscoveryUpdate(e),!0)}async _refreshDiscoverySwarming(){if(await this._discoveryLock.lock()&&!this.closing)try{const e=await this.internalEnterLocalTransaction();try{for await(const{discoveryPublicKey:t}of e.find("@local/background-sync-by-dirty",{reverse:!0})){const e=await this.local.get("@local/rooms-by-discovery",{capabilities:{discoveryKeyPair:{publicKey:t}}});if(!e||e.status.isRemoved)continue;const i=s.encode(e.discoveryKey);if(this._discovering.has(i))continue;const o=this.internalOpenExistingRoom(e.roomKey);this._discovering.set(i,{updated:!1,room:o,roomInfo:e}),this._startDiscoverySwarming(e.capabilities.swarmTopic,i)||this.internalReleaseRoom(o)}await e.flush()}finally{await this.internalExitLocalTransaction()}}catch(e){this.onerror(e)}finally{this._discoveryLock.unlock()}}async scheduleDiscoverySwarmingFromDiscoveryKey(e){const t=await this.local.get("@local/rooms-by-discovery-key",{discoveryKey:e});if(t)return this.scheduleDiscoverySwarming(t.capabilities.discoveryKeyPair.publicKey)}async scheduleDiscoverySwarming(e){const t=await this.internalEnterLocalTransaction();try{const i=await t.get("@local/background-sync",{discoveryPublicKey:e}),s=i?.lastSynced||0;await t.insert("@local/background-sync",{discoveryPublicKey:e,timestamp:Date.now(),lastSynced:s,dirty:!0}),await t.flush()}finally{await this.internalExitLocalTransaction()}this._refreshDiscoverySwarming()}async internalMarkRoomUpdated(e,t,i){const s=await this.internalEnterLocalTransaction();try{await s.insert("@local/room-updates",{roomKey:e,head:t,timestamp:Date.now(),incremental:i}),await s.flush()}finally{await this.internalExitLocalTransaction()}}async internalBlobsActiveInRoom(e){const t=s.encode(e),i=this.rooms.get(t);if(!i)return;const o=await this.local.get("@local/rooms",{roomKey:e});if(!o)return;const{capabilities:a}=o;if(!a||!a.mailboxKeyPair)return;try{await i.ready()}catch{return}if(!i.externalBlobs)return;const r=await this._getMirrorKeys(e,o);i.closing||this.blindPeering.addCoreBackground(i.externalBlobs.core,{target:a.mailboxKeyPair.publicKey,keys:r,pick:1})}async _getMirrorKeys(e,t){if(this.debug.disableMirrors)return[];if(!e)return this._blindPeerKeys;const i=t||await this.local.get("@local/rooms",{roomKey:e});return i&&i.config.blindPeers?.length?i.config.blindPeers:this._blindPeerKeys}_relayThrough(e){return e||this.swarm.dht.randomized?this._relayKeys:null}_callSwarmRelayThrough(e,t){return e||this.swarm.dht.randomized||t.dht.randomized?this._relayKeys:null}internalCreateCallSwarm(){return new n({...this._swarmOpts,keyPair:this.callSwarmKeyPair,dht:this.swarm.dht,relayThrough:this._callSwarmRelayThrough.bind(this)})}async internalStartCall(e,t){if(this.activeCall)throw me.CALL_SWARM_ACTIVE("",{callId:this.activeCall.callId});const i=s.decode(e),o=await this.local.get("@local/rooms",{roomKey:i}),a=f.randomBytes(32),r=await this.dispatch(e,"@keet/start-call",{version:3,callKey:a,encryptionKey:o.encryptionKey,swarmKey:this.callSwarmKeyPair.publicKey,streamInputs:Ee(t)});if(!r)throw me.CALL_DISPATCH_REJECTED("",{roomId:e,op:"@keet/start-call"});return await this.internalStartActiveCall(e,s.encode(a)),r}async internalJoinCall(e,t,i){if(this.activeCall)throw me.CALL_SWARM_ACTIVE("",{callId:this.activeCall.callId});const o=s.decode(t),a=this.internalOpenExistingRoom(e);let r=null;try{a.opened||await a.ready(),r=await a.db.get("@keet/call-devices",{callKey:o,deviceKey:a.deviceKey})}finally{await this.internalReleaseRoom(a)}let n=null;if(!r&&(n=await this.dispatch(e,"@keet/join-call",{callKey:o,swarmKey:this.callSwarmKeyPair.publicKey,streamInputs:Ee(i)}),!n))throw me.CALL_DISPATCH_REJECTED("",{roomId:e,callId:t,op:"@keet/join-call"});return await this.internalStartActiveCall(e,t),n}async internalLeaveCall(e,t){const i=await this.dispatch(e,"@keet/leave-call",{callKey:s.decode(t)});if(!i)throw me.CALL_DISPATCH_REJECTED("",{roomId:e,callId:t,op:"@keet/leave-call"});return this.activeCall&&this.activeCall.callId===t&&await this.internalEndActiveCall(),i}async internalEndCall(e,t){const i=await this.dispatch(e,"@keet/end-call",{callKey:s.decode(t)});if(!i)throw me.CALL_DISPATCH_REJECTED("",{roomId:e,callId:t,op:"@keet/end-call"});return this.activeCall&&this.activeCall.callId===t&&await this.internalEndActiveCall(),i}async internalStartActiveCall(e,t){if(this.activeCall)throw me.CALL_SWARM_ACTIVE("",{callId:this.activeCall.callId});if(this.suspended)throw me.CALL_SWARM_SUSPENDED("",{callId:t});const i=new _e(this,e,t);this.activeCall=i;try{await i.ready()}catch(e){throw this.activeCall=null,e}return i}internalEndActiveCall(){if(!this.activeCall)throw me.CALL_NOT_ACTIVE();const e=this.activeCall;this.activeCall=null;const t=e.close();return t.catch(this.onerror),t}internalSyncActiveCall(e){if(this.activeCall&&this.activeCall.roomId===e)return this.activeCall.sync()}internalVerboseLog(...e){const t=new Date;this.logger.info(t,...e)}internalTime(...e){this.isTesting||console.time(...e)}internalTimeLog(...e){this.isTesting||console.timeLog(...e)}internalTimeEnd(...e){this.isTesting||console.timeEnd(...e)}_handleConnection(e){if(this.closing)return void e.on("error",()=>{});const t=s.encode(e.publicKey),i=s.encode(e.remotePublicKey),o=Date.now();let a=0;this.internalVerboseLog(`got ${e.isInitiator?"client":"server"} connection from ${t} to ${i}`);let r=null;e.once("error",e=>{r=e}),e.once("close",()=>{this.internalVerboseLog(`connection closed to ${i}, with err: ${r} (${Date.now()-o} ms ${a} bytes recv)`)}),e.on("data",e=>{a+=e.byteLength}),this.protomuxWakeup.addStream(e),this.rootCorestore.replicate(e)}async _createInitialIdentity(){const t=e.generateMnemonic(),i=this.tasks.restoreIdentity(Pe,{mnemonic:t,recover:!1});await i.run()}async _loadIdentity(){let e=await this.local.get("@local/identity");if(e||await this._createInitialIdentity(),e=await this.local.get("@local/identity"),this.privateProfile)return;const t=await this.local.get("@local/rooms",{roomKey:e.privateProfileKey});t&&this.swarmWorker.startActiveSwarming(t.capabilities.swarmTopic),this.privateProfile=await this.internalOpenPrivateProfile({opening:!0}),this._shortenDeviceAttestations().catch(this.onerror),await this.internalRefreshUnreads()}async internalResyncPrivateProfile(){if(!this.privateProfile)return null;const e=[],t=[];for await(const s of this.privateProfile.db.find("@keet-private-id/rooms")){if(this.closing)return null;if(s.identitySwapV2)continue;if(this.internalIsRoomDisabled(s.roomKey))continue;const[o,a,r]=await Promise.all([this.local.get("@local/rooms",{roomKey:s.roomKey}),this.local.get("@local/writers",{mailbox:s.mailbox}),this.local.get("@local/room-preferences",{roomKey:s.roomKey})]);s.status.isActive?i(s,o,a)&&e.push(s):!o||r&&!r.status.isActive||t.push(s)}if(this.closing)return null;if(t.length>0){const e=await this.internalEnterLocalTransaction();try{for(const i of t){const t={roomKey:i.roomKey,status:i.status};await e.insert("@local/room-preferences",t);const s=await e.get("@local/rooms",t);s&&(s.preferences=t.status,await e.insert("@local/rooms",s))}await e.flush()}finally{await this.internalExitLocalTransaction()}}for(const t of e)this.tasks.joinRoomKeys(t.mailbox,{keys:t,activeClock:t.activeClock,isReadOnly:t.status.isReadOnly}).runBackground();for(const e of t)this.tasks.leaveRoom(e.mailbox,{roomKey:e.roomKey}).runBackground();return{joins:e.length,leaves:t.length};function i(e,t,i){if(!t)return!0;if(e.status.isReadOnly||t.status?.isWritable)return!1;if(!(e.initiator&&i&&v.equals(i.key,e.initiator.key))){if(!i||i.pending)return!0;if((i.activeClock||0)<(e.activeClock||0))return!0}return!!t.selfMember?.status.isActive&&!t.selfDevice?.status.isActive}}async _resyncPrivateProfileOnBoot(){if(this.readOnly||this.closing||!this.privateProfile)return;const[e,t]=await Promise.all([this.local.get("@local/identity"),this.local.get("@local/pending-recovery")]);!e||e.swapV2||t||await this.internalResyncPrivateProfile()}async _shortenDeviceAttestations(){const e=await this.local.get("@local/identity");if(!e)return;const i=this.privateProfile.engine.local.key,s=k.decode(t,e.deviceProof).chain.length;for await(const o of this.privateProfile.db.find("@keet-private-id/device-identities")){const a=v.equals(o.deviceKey,i),r=k.decode(t,o.deviceProof),n=r.chain.length,l=r.chain[n-1].publicKey;if(!a&&s+1<n){const t=Le(e,o.deviceKey,l);await this.privateProfile.dispatch("@keet-private-id/shorten-device-attestation",{deviceKey:o.deviceKey,deviceProof:t.deviceProof,writerProof:t.privateProfileIdentity.proof})}if(a&&k.decode(t,o.deviceProof).chain.length<s){const t=await this.internalEnterLocalTransaction();try{const i=await t.get("@local/identity");v.equals(i.publicKey,e.publicKey)&&(i.deviceProof=o.deviceProof,i.proofShortenedAt=Date.now(),await t.insert("@local/identity",i),await t.flush())}finally{await this.internalExitLocalTransaction()}}}}async internalDispatchUpgrade(e,t){if(!e||!t)return;const i=this.internalOpenExistingRoom(e);try{const e=this.batch(i.id);if(0===await e.dispatch("@keet/upgrade",{version:t}))return;if(t>=14){const s=await i.db.get("@keet/system-digest"),o="@keet/migrate-abi",a={header:{timestamp:e.timestamp},from:s?.abi.version,to:t,buildType:this.buildType},r=t<this.abi.min?this.abi.min:t,n=(await this.contractBundles.loadBundle(r)).encodeOperation(o,a);await e.append(n)}await e.flush()}finally{this.internalReleaseRoom(i)}}async _bumpCache(){if(!this.closing&&!0!==this.debugDisableCacheBump)try{if(!await this._cacheLock.lock())return;try{if(this.closing||!0===this.debugDisableCacheBump)return;await this._processCache()}finally{this._cacheLock.unlock()}}catch(e){this.onerror(e)}}async _processCache(){const e=[];for(;this._pendingTraces.length>0;){const t=this._pendingTraces.pop();e.push(this._updateCache(t))}const t=await Promise.all(e),i=await this.internalEnterLocalTransaction();try{for(const e of t)e&&await i.insert(e.collection,e.record);await i.flush()}finally{await this.internalExitLocalTransaction()}}async _updateCache({roomKey:e,collection:t,record:i}){if("@keet/members"===t){if(i?.profile.avatar){const t=this.internalOpenExistingRoom(e);try{await Y(t.db,t.blobs,i.profile.avatar)}catch{return null}finally{this.internalReleaseRoom(t)}}return{collection:"@local/members",record:i}}return"@keet/devices"===t?{collection:"@local/devices",record:{roomKey:e,device:i}}:null}_cacheRoomReads(e,t,i){this._pendingTraces.push({roomKey:e,collection:t,record:i}),this._bumpCache()}_maybeLogAutobeeApply(e,t,i,o,a){try{const r=s.encode(a),n=this.rooms.get(r),l=n?n.getQuickStats():null;this.logger.verboseDebug("[autobeeApply]",{"op.name":e.name,"op.id":e.id,dryRun:t,"nodes.length":i,abi:o,roomId:r,"roomStats.view":l?.view,"roomStats.system":l?.system,"roomStats.base":l?.base})}catch(e){console.error("Failed to log autobee apply",e)}}_autobeeTrace({type:e,value:t}){if("@keet/add-device"!==e)return;const{request:i,response:s,invitation:o}=t,a=o?.metadata.expiration;if(a>0&&a<Date.now())return;const r=i.member.profile.mailbox,n=i.keyPair;r&&n&&this._processAutobeeTrace(r,n,s).catch(this.onerror)}async _processAutobeeTrace(e,t,i){this.onlineBoot.opened||await this.onlineBoot.ready();const s=await this.local.get("@local/identity"),o=await this.local.get("@local/rooms",{roomKey:s.privateProfileKey});v.equals(e,o.capabilities.mailboxKeyPair.publicKey)||this.tasks.joinRoomResponse(t.publicKey,{mailbox:e,keyPair:t,response:i}).runBackground()}async _backgroundDrain(e){let t=await e.next();try{for(;!t.done&&!this.closing;){const i=await t.value;if(this.closing)return;t=await e.next(i)}}finally{!t.done&&e.return&&await e.return()}}async _safeLoadLegacyUnreads(){try{const e=[];for await(const t of this.local.find("@local/rooms")){const i=await this.local.get("@local/unreads",{roomKey:t.roomKey});i&&e.push(i)}return e}catch{return[]}}async*_migrateUnreadAnchors(){const e=yield this._safeLoadLegacyUnreads();try{const t=yield this.internalEnterLocalTransaction(),i=this.unreads.write(),s=Date.now();for(const{roomKey:o,count:a}of e)i.tryPut(o,{length:a+1,timestamp:s}),yield t.delete("@local/unreads",{roomKey:o});yield i.flush(),yield t.flush()}finally{yield this.internalExitLocalTransaction()}}async*_internalRefreshUnreads(){if(this.privateProfile&&(yield this.privateProfile.db.get("@keet/system-digest"))){yield this.privateProfile.internalEnsureUpgraded({opening:!0});try{if(!(yield this._unreadsLock.lock()))return;const e=[];let t=!0;const i=yield this.privateProfile.db.find("@keet-private-id/unreads-v2").toArray();for(const{deviceKey:s,key:o}of i)v.equals(s,this.privateProfile.engine.local.key)&&(t=!1),e.push(o);if(this.unreads&&function(e,t){if(e.length!==t.length)return!1;for(let i=0;i<e.length;i++)if(!v.equals(e[i],t[i]))return!1;return!0}(e,this.unreads.keys))return;this.unreads&&(yield this.unreads.close(),this.unreads=null);const s=this.privateProfile,o=s.engine.encryptionKey,a=this.store.namespace(s.key),r=a.namespace("keet-unreads");yield a.close();const n=yield s.engine.wakeupKey;this.unreads=new L(r,e,{blindPeering:this.blindPeering,encryptionKey:o,target:n}),this.unreads.on("remote-update",()=>this.emit("unread-anchors-changed")),yield this.unreads.ready(),this.emit("unread-anchors-changed"),t&&(yield this._backgroundDrain(this._migrateUnreadAnchors()),yield this.privateProfile.dispatch("@keet-private-id/add-unreads-key",{key:this.unreads.local.key}))}finally{this._unreadsLock.unlock()}}}internalRefreshUnreads(){return this._disableUnreadSync?Promise.resolve():this._backgroundDrain(this._internalRefreshUnreads())}async internalLoadDiscoveryCore(){try{const e=await this.privateProfile.db.get("@keet-private-id/identity");if(!e||!e.discoveryCoreKey||this.closing)return;if(this.discoveryCore)return;this.discoveryCore=this.store.get({key:e.discoveryCoreKey}),await this.discoveryCore.ready(),this.blindPeering.addCoreBackground(this.discoveryCore,{pick:3,priority:2})}catch(e){this.onerror(e)}}async internalCreateDiscoveryCore(e){const t=e.getProfileDiscoveryEncryptionKey(),i={version:2,signers:[{publicKey:e.profileDiscoveryKeyPair.publicKey}]};return this.discoveryCore&&await this.discoveryCore.close(),this.discoveryCore=this.store.get({keyPair:e.profileDiscoveryKeyPair,valueEncoding:le,encryptionKey:t,manifest:i}),await this.discoveryCore.ready(),this.blindPeering.addCoreBackground(this.discoveryCore,{pick:3,priority:2}),this.discoveryCoreSession=this.swarm.join(this.discoveryCore.discoveryKey,{server:!1,client:!0}),!0}async internalDryRun(e,t,i){const s=i.map((t,i)=>({length:e.engine.local.length+1+i,from:e.engine.local,value:t,heads:[],version:0}));return!!await e.engine.dryRun(s,t)}internalGetHeads(e,t){const i=[];let s=!1;if(t)for(const o of t.engine.heads())e&&v.equals(o.key,e.key)?(s=!0,i.push({key:o.key,length:Math.max(o.length,e.length)})):i.push(o);return e&&!s&&i.push(e),i}async internalGetKeyPair(e){const t=await this.local.get("@local/writers-by-room-key",{roomKey:e});return t?t.pending?null:t.keyPair:null}async internalEnsureWriter(e,t,{rotate:i=!1,activeClock:s=0}={}){let o=null;if(!i){const i=await this.local.get("@local/writers",{mailbox:t});if(i&&i.roomKey&&v.equals(i.roomKey,e))return i;o=i}if(o)o.roomKey=e;else{const i=f.keyPair(),a=this.store.get({keyPair:i});await a.ready(),o={keyPair:i,key:a.key,mailbox:t,responseKeyPair:null,roomKey:e,pending:!0,activeClock:s},await a.close()}const a=await this.internalEnterLocalTransaction();try{await a.insert("@local/writers",o),await a.flush()}finally{await this.internalExitLocalTransaction()}return o}async internalGetMailbox(e){const t=await this.local.get("@local/rooms",{roomKey:e});if(t)return{key:t.capabilities.mailboxKeyPair.publicKey,discoveryKey:t.capabilities.wakeupTopic};const i=await this.local.get("@local/writers-by-room-key",{roomKey:e});return i?{key:i.mailbox,discoveryKey:j(i.mailbox)}:null}async internalGetEncryptionKey(e){const t=await this.local.get("@local/rooms",{roomKey:e});return!t||t.status.isRemoved?null:t.encryptionKey}getAutobeeMigrationInfo(e){return this.local.get("@local/autobee-migrations",{roomKey:e})}internalReleaseRoom(e){if(0===e.refs)throw new Error("Cannot release a room that was not previously opened");e.refs-=1}internalOpenExistingRoom(e,t={}){return this.internalOpenRoom(s.decode(e),null,t)}async internalOpenPrivateProfile({opening:e=!1}={}){e||this.onlineBoot.opened||await this.onlineBoot.ready();const t=await this.local.get("@local/identity"),i=this.internalOpenExistingRoom(t.privateProfileKey,{bigBatches:!0,priority:2});return await i.ready(),this.privateProfile&&v.equals(i.key,this.privateProfile.key)||(this.privateProfile&&this.internalReleaseRoom(this.privateProfile),this.privateProfile=this.internalOpenExistingRoom(t.privateProfileKey,{bigBatches:!0,priority:2}),this.unreadsLoading=this.internalRefreshUnreads().catch(()=>null)),this.internalLoadDiscoveryCore(),i}async internalSafeSwapPrivateProfiles(){try{const e=await this.internalOpenPrivateProfile();this.internalReleaseRoom(e)}catch(e){this.onerror(e)}}async internalFeatureSupported(e,t){const i=s.decode(e),o=await this.local.get("@local/rooms",{roomKey:i});return!!o&&this.versions.isFeatureSupported(o.digest.abi.version,t)}internalOpenRoom(e,t,{bootFrom:i,mailbox:o,keyPair:a,bigBatches:r,priority:n=1}={}){if(this.closing)throw new Error("Keet is closing");const l=s.encode(e),c=this.rooms.get(l);if(c)return c.idleTicks=0,c.refs+=1,c;const h=f.discoveryKey(e),d=this.store.namespace(h);let y=null,u=null,m=null;o||(u=this.internalGetMailbox(e),u.catch(this.onerror)),a||(y=this.internalGetKeyPair(e),y.catch(this.onerror)),t||(m=this.internalGetEncryptionKey(e),m.catch(this.onerror)),o&&(o={key:o,discoveryKey:f.discoveryKey(o)});const p={backoff:this.backoff,encryptionKey:t||m,wakeupCapability:o||u,keyPair:a||y,fastForward:!this.debug.disableFastForward&&{boot:i},ackThreshold:this.ackThreshold,ackInterval:this.ackInterval,wakeup:this.protomuxWakeup,optimistic:!0,encrypted:!0,bigBatches:!!r,priority:n},g=Te.create(this,d,l,e,h,p);return this.rooms.set(l,g),g.refs+=1,g}async updateDevice({name:e,deviceId:t}={}){this.onlineBoot.opened||await this.onlineBoot.ready();const i={swarmKey:this.swarm.keyPair.publicKey,name:e,isMobile:this.isMobile},o=await this.internalOpenPrivateProfile();try{if(t&&!v.equals(s.decode(t),o.deviceKey)){if(!await o.dispatch("@keet/update-device",{key:s.decode(t),profile:{name:e}}))throw me.UPDATE_DEVICE_FAILED("Failed to update device",{deviceId:t})}else await o.dispatch("@keet/update-device",{profile:i})}finally{this.internalReleaseRoom(o)}}async internalUpdateProfile(e,{forceUpdateRegistry:t=!1,opening:i=!1}={}){const s=await this.internalGetIdentityInfo({opening:i});if(this.closing)return;if(this._rejectAnonymousProfiles&&!e.displayName&&!t)throw new Error("A member must have a profile name");const o=this.internalCreateMemberUpdate(s,{profile:e}),a=await this.internalOpenPrivateProfile({opening:i});if(this.closing)return;let r=null;try{r=await a.dispatch("@keet/update-member",{member:o})}finally{this.internalReleaseRoom(a)}(r||t)&&o.profile.username&&this.userRegistry?.internalUpdateRegistryMember(o)}async updateProfile(e={}){return this.onlineBoot.opened||await this.onlineBoot.ready(),this.internalUpdateProfile(e)}async internalVerifyOptimisticCore(e){return!await e.getUserData("referrer")}async internalAppendOptimisticOperation(e,t){const i=this.store.get({key:e,exclusive:!0});await i.ready();try{if(!await this.internalVerifyOptimisticCore(i))return!1;if(0!==i.length)return!1;const{length:e}=await i.append(t,{maxLength:1});return 1===e}finally{await i.close()}}async internalCreateRoom(e={}){const t=e.seed||f.randomBytes(32),i=e.keyPair||f.keyPair(),s=e.encryptionKey||f.randomBytes(32),o=F(i.publicKey),a=f.discoveryKey(o),r=$(o,t),n=r.mailboxKeyPair.publicKey,l=await this.internalEnterLocalTransaction();try{await l.insert("@local/writers",{mailbox:n,keyPair:i,key:o,roomKey:o}),await l.flush()}finally{await this.internalExitLocalTransaction()}const c={header:{timestamp:Date.now()},digest:{abi:{version:this.isExperimental?this.abi.production:this.abi.autoupgrade},creationTime:Date.now(),settings:{roomType:1,...e.settings,isExperimental:this.isExperimental,buildType:this.buildType}},capabilities:{seed:t},config:e.config,identity:e.identity,initialMembers:e.initialMembers,bootstrapDevice:e.bootstrapDevice},h=this.tasks.createRoom(a,{roomKey:o,encryptionKey:s,mailbox:n,keyPair:i,genesis:c,isPrivateProfile:!!e.isPrivateProfile});return await h.run(),this.discovery&&this.discovery.subscribe(r.discoveryKeyPair.publicKey,{since:Date.now()}),this.swarmWorker.refresh().catch(De),this.internalOpenExistingRoom(o)}async createRoom(e){this.opened||await this.ready();const t=await this.internalCreateRoom(e);return this.internalReleaseRoom(t),t.key}async leaveRoom(e){this.onlineBoot.opened||await this.onlineBoot.ready();const t=await this.local.get("@local/identity"),i=this.internalOpenExistingRoom(e);await i.ready();try{await i.dispatch("@keet/remove-member",{key:t.publicKey})}catch(e){this.onerror(e)}finally{this.internalReleaseRoom(i)}await this.privateProfile.dispatch("@keet-private-id/update-room-status",{roomKey:e,key:Q.IsActive,status:{isActive:!1}})}async activateRoom(e){if(this.closing)return;const t=await this.local.get("@local/rooms",{roomKey:e});if(!t||this.closing)return;const i=this.internalOpenExistingRoom(e);this.activeRooms.has(i)?this.internalReleaseRoom(i):(this.activeRooms.add(i),this.swarmWorker.startActiveSwarming(t.capabilities.swarmTopic),await i.ready())}async deactivateRoom(e){const t=this.internalOpenExistingRoom(e);if(!this.activeRooms.has(t))return void this.internalReleaseRoom(t);const i=await this.local.get("@local/rooms",{roomKey:e});this.activeRooms.delete(t),this.swarmWorker.stopActiveSwarming(i.capabilities.swarmTopic),this.internalReleaseRoom(t),this.internalReleaseRoom(t)}async internalGetIdentityInfo({opening:e=!1}={}){e||this.onlineBoot.opened||await this.onlineBoot.ready();const t=await this.local.get("@local/identity");if(!t)throw new Error("Identity does not exist");const i=await this.local.get("@local/rooms",{roomKey:t.privateProfileKey});if(!i)throw new Error("Private profile does not exist");return{identity:t,privateProfile:i}}internalGetMemberUpdate(e,{profile:t}={}){const i=e.privateProfile;return i?.selfMember?i.selfMember.proofs?{profile:i.selfMember.profile,attestation:{memberKey:i.selfMember.key,proof:i.selfMember.proofs.profileAttestation}}:this.internalCreateMemberUpdate(e):null}internalAttestData(t,i){return e.attestData(t,i.deviceKeyPair,i.deviceProof)}internalCreateDeviceUpdate(e,t,{isPrivateProfile:i=!1,buildType:s=null}={}){const o=e.identity;let a=null;e.privateProfile?.selfDevice&&i&&(a=e.privateProfile.selfDevice.profile.name);const r=e.privateProfile?.digest.settings.buildType,n=ce.Production===r;return{profile:{swarmKey:this.swarm.keyPair.publicKey,name:a,isMobile:this.isMobile,buildType:n&&null===s?null:r},attestation:{memberKey:o.publicKey,proof:this.internalAttestData(t,o)}}}internalCreateMemberUpdate(t,{profile:i}={}){const s=t.privateProfile?.selfMember?.profile||null,o=t.privateProfile?.capabilities.mailboxKeyPair.publicKey||i.mailbox,a=t.identity,r={...s,...i,mailbox:o,timestamp:Date.now()},n=oe("@keet/member-profile",r);return{profile:r,attestation:{memberKey:a.publicKey,proof:e.attestData(n,a.deviceKeyPair,a.deviceProof)}}}internalGetLinkInfo(e){try{return this.internalDecodeInvitation(e)}catch{}try{return this.internalDecodeContactInfo(e)}catch{}return null}internalDecodeInvitation(e){try{const t="string"==typeof e?b.decode(e):e;return ae("@keet/sendable-invitation",t)}catch{throw me.INVALID_INVITE(`Invalid invite: ${e}`)}}internalDecodeContactInfo(e){try{const t=v.isBuffer(e)?e:v.from(e,"base64");return ae("@keet/member-contact-info",t)}catch{throw me.INVALID_CONTACT_INFO(`Invalid contact info: ${e}`)}}internalEncodeContactInfo(e){return oe("@keet/member-contact-info",e)}internalCreateContactInfoStruct(t,i,s={}){const o={...i.selfMember.profile,...s},a=oe("@keet/member-profile",o),r={profile:o,attestation:{memberKey:i.selfMember.key,proof:e.attestData(a,t.deviceKeyPair,t.deviceProof)}};return this.internalEncodeContactInfo(r)}async wakeup(){this.state=ve.WAKEUP,this.internalVerboseLog("[Keet Core]","await .ready"),this.opened||await this.ready();const e=[this.resume(),this.swarmWorker.background()];this.internalVerboseLog("[Keet Core]","await all promise"),await Promise.allSettled(e),this.internalVerboseLog("[Keet Core]","promise settled")}async presuspend(){this.state=ve.PRESUSPEND;const e=Date.now();this.opened||await this.ready(),this.swarmWorker.setServerMode(!1),this.internalVerboseLog("[Keet Core]",`presuspend done elapsed=${Date.now()-e}ms`)}async suspend(){if(this.internalVerboseLog("[Keet Core]","suspend called"),this.state===ve.SUSPEND)return;this.state=ve.SUSPEND,this.opened||await this.ready(),this.internalVerboseLog("[Keet Core]","suspend post ready");const e=Date.now(),t=(t,i)=>i.finally(()=>{this.internalVerboseLog("[Keet Core]",`suspend settled ${t} elapsed=${Date.now()-e}ms`)}),i=[t("dhtCache",this._flushDHTCache())];this.blindPeering&&(this.internalVerboseLog("[Keet Core]","suspend adding blind peering"),i.push(t("blindPeering",this.blindPeering.suspend()))),this.push&&(this.internalVerboseLog("[Keet Core]","suspend push"),i.push(t("push",this.push.suspend()))),this.discovery&&(this.internalVerboseLog("[Keet Core]","suspend discovery"),i.push(t("discovery",this.discovery.suspend()))),this.bluetooth&&(this.internalVerboseLog("[Keet Core]","suspend bluetooth"),i.push(t("bluetooth",this.bluetooth.suspend()))),await Promise.allSettled(i),this.internalVerboseLog("[Keet Core]","all done"),await this.rpcClient.suspend(),this.internalVerboseLog("[Keet Core]","rpc suspended"),this.activeCall&&await this.activeCall.suspend(),this.internalVerboseLog("[Keet Core]","active call suspend");const s=this.swarm.connections.size;this.internalVerboseLog("[Keet Core]",`suspending swarm connections=${s}`);const o=Date.now();await this.swarm.suspend(),this.internalVerboseLog("[Keet Core]","swarm suspend"),this.internalVerboseLog("[Keet Core]",`swarm suspended elapsed=${Date.now()-o}ms connections=${this.swarm.connections.size}`),await this.files.server.suspend(),this.internalVerboseLog("[Keet Core]","files suspend"),await this.store.suspend({log:this.internalVerboseLog.bind(this)}),this.internalVerboseLog("[Keet Core]","store suspended, all done")}async resume(){if(this.internalVerboseLog("[Keet Core]","resume called"),this.state===ve.RESUME)return;this.state=ve.RESUME,this.internalVerboseLog("[Keet Core]","await this.ready"),this.opened||await this.ready(),this.internalVerboseLog("[Keet Core]","resume store"),await this.store.resume(),this.internalVerboseLog("[Keet Core]","resume file server"),await this.files.server.resume(),this.internalVerboseLog("[Keet Core]","resume swarm"),this.readOnly||await this.swarm.resume(),this.internalVerboseLog("[Keet Core]","resume active call"),this.activeCall&&!this.readOnly&&await this.activeCall.resume(),this.internalVerboseLog("[Keet Core]","resume rpc client"),await this.rpcClient.resume();const e=[];this.blindPeering&&!this.readOnly&&e.push(this.blindPeering.resume()),this.push&&e.push(this.push.resume()),this.discovery&&e.push(this.discovery.resume()),this.bluetooth&&!this.readOnly&&e.push(this.bluetooth.resume()),this.swarmWorker.setServerMode(!0),e.push(this.swarmWorker.unbackground()),this.internalVerboseLog("[Keet Core]","await all promises"),await Promise.allSettled(e),this.internalVerboseLog("[Keet Core]","all promises settled")}async _createInvitation(e,t){let{expiration:i=0,canAdmin:s=!1,canModerate:o=!1,identityPublicKey:a=null,requiresConfirmation:r=!1,readOnly:n=!1}=t;if("string"==typeof i)if("2h"===i)i=Date.now()+72e5;else if("2d"===i)i=Date.now()+1728e5;else{if("2w"!==i)throw me.UNKNOWN_EXPIRATION(`Unknown expiration: ${i}`,{roomKey:e.key});i=Date.now()+12096e5}await e.ready();const[{seed:l},c,h]=await Promise.all([e.db.get("@keet/room-capabilities"),z(e.db,e.deviceKey),e.db.get("@keet/system-digest")]);if((o||s)&&!c?.status.isModerator)throw me.MODERATORS_ONLY("Only moderators can create moderator invitations",{roomKey:e.key});const d=$(e.key,l),y=this.swarm.dht.toArray({limit:3}),u=await e.internalGetBootFrom();if(n)return oe("@keet/sendable-invitation",{"readonly-v2":{roomKey:e.key,encryptionKey:e.encryptionKey,seed:l,bootFrom:u},additionalNodes:y});const m={key:e.key,encryptionKey:e.encryptionKey,bootFrom:u},{invitation:p,seed:g}=H(m,{requiresConfirmation:r,expiration:i,canModerate:o,canAdmin:s});await e.dispatch("@keet/add-invitation",{invitation:p});const w=(await e.db.get("@keet/invitations",{id:p.id})).anchor,f=d.mailboxKeyPair.publicKey,b={identity:null,room:null,contact:null,additionalNodes:y,buildType:h.settings.buildType};return a?b.identity={mailbox:f,invitationSeed:g,anchor:w,identityPublicKey:a}:b.room={mailbox:f,invitationSeed:g,anchor:w},oe("@keet/sendable-invitation",b)}async createRoomInvitation(e,t={}){this.opened||await this.ready();const i=s.decode(e),o=this.internalOpenExistingRoom(i);try{const e=await this._createInvitation(o,t);return b.encode(e)}finally{this.internalReleaseRoom(o)}}async createIdentityInvitation(){this.onlineBoot.opened||await this.onlineBoot.ready();const e=await this.local.get("@local/identity"),t=this.internalOpenExistingRoom(e.privateProfileKey);this.blindPeering.addAutobaseBackground(t.engine.auto,{priority:2});try{const i=await this._createInvitation(t,{expiration:"2h",canAdmin:!0,canModerate:!0,requiresConfirmation:!0,identityPublicKey:e.publicKey});return b.encode(i)}finally{this.internalReleaseRoom(t)}}async _startPairing(e,{deviceKeyPair:t,buildType:i=null}={}){this.onlineBoot.opened||await this.onlineBoot.ready();const o=W(e.invitationSeed),a=e.invitationSeed,r=e.mailbox,n=e.anchor,l={key:null,seedId:s.encode(a)},c=await this.local.get("@local/rooms-by-mailbox",{capabilities:{mailboxKeyPair:{publicKey:r}}});let h=null;if(c){if(c.selfMember?.status.isRemoved)throw me.MEMBER_REMOVED("Removed member cannot rejoin");h=await this.local.get("@local/room-preferences",{roomKey:c.roomKey})}if(c?.status.isWritable&&h&&h.status.isActive&&!h.status.isReadOnly)return l.key=s.encode(c.roomKey),l;const d=f.keyPair(),y=await this.store.createKeyPair(J(d.publicKey)),u=this.store.get({keyPair:y,active:!1});await u.ready();const m={key:u.key,length:1};await u.close();let p=null;t&&(p={writerKey:u.key,publicKey:e.identityPublicKey,devicePublicKey:t.publicKey});const g={id:o,mailbox:r,inviter:n,identity:p,initiator:m,responseKeyPair:d,invitationSeed:a,buildType:i},w=await this.privateProfile.db.find("@keet-private-id/pending-invitations").toArray();for(const e of w)v.equals(e.mailbox,g.mailbox)&&await this.privateProfile.dispatch("@keet-private-id/remove-pending-invitation",e);return await this.privateProfile.dispatch("@keet-private-id/add-pending-invitation",{invitation:g}),l}async _openReadOnlyRoom({roomKey:e,encryptionKey:t,seed:i,fastForwardTo:o,bootFrom:a}){this.onlineBoot.opened||await this.onlineBoot.ready();const{mailboxKeyPair:r,swarmTopic:n}=$(e,i);!a&&o&&(a={key:o,length:0}),await this.internalEnsureWriter(e,r.publicKey);const l=this.internalOpenRoom(e,t,{mailbox:r.publicKey,bootFrom:q.getBootOption(a,o)});return this.swarmWorker.startDiscoverySwarming(n,{roomId:s.encode(e)}),await l.ready(),await this.privateProfile.dispatch("@keet-private-id/add-room-v2",{roomKey:e,encryptionKey:t,mailbox:r.publicKey,bootFrom:a,isReadOnly:!0}),{key:s.encode(e),seedId:s.encode(i)}}async startPairingRoom(e,t){this.onlineBoot.opened||await this.onlineBoot.ready();const i=this.internalDecodeInvitation(e),s=await this.local.get("@local/rooms",{roomKey:this.privateProfile.key}),o=i.buildType,a=s.digest.settings.buildType||ce.Production;if(o&&o!==a)throw me.INVALID_BUILD_TYPE();const r=i.readonly||i["readonly-v2"];if(r)return this._openReadOnlyRoom(r);if(!i.room)throw me.INVALID_INVITATION_TYPE("Invalid room invitation");return this._startPairing(i.room,{...t,buildType:o})}async startPairingIdentity(e){this.onlineBoot.opened||await this.onlineBoot.ready();const t=this.internalDecodeInvitation(e),i=await this.local.get("@local/rooms",{roomKey:this.privateProfile.key}),s=t.buildType,o=i.digest.settings.buildType||ce.Production;if(s&&s!==o)throw me.INVALID_BUILD_TYPE();if(!t.identity)throw me.INVALID_INVITATION_TYPE("Invalid device sync invitation");const a=G(t.identity.identityPublicKey),r=await this.store.createKeyPair(a);return this._startPairing(t.identity,{deviceKeyPair:r,buildType:s})}async stopPairing(e){this.opened||await this.ready();const t=await this.internalOpenPrivateProfile();try{if(!await t.db.get("@keet-private-id/pending-invitations",{id:e}))return;await t.dispatch("@keet-private-id/remove-pending-invitation",{id:e})}finally{this.internalReleaseRoom(t)}}async rotateWriter(e){this.onlineBoot.opened||await this.onlineBoot.ready();const[t,i,s]=await Promise.all([this.local.get("@local/writers-by-room-key",{roomKey:e}),this.local.get("@local/rooms",{roomKey:e}),this.privateProfile.db.get("@keet-private-id/rooms",{roomKey:e})]);i&&t&&s&&(await this.internalEnsureWriter(e,s.mailbox,{rotate:!0,activeClock:s.activeClock}),this.tasks.joinRoomKeys(s.mailbox,{keys:s,activeClock:s.activeClock,isReadOnly:s.status.isReadOnly}).runBackground())}async getRecoveryPhrase(){this.onlineBoot.opened||await this.onlineBoot.ready();const e=(await this.local.get("@local/identity")).entropy;return e?E({entropy:e}):null}async getRecoveryEntropy(){return this.onlineBoot.opened||await this.onlineBoot.ready(),(await this.local.get("@local/identity")).entropy||null}async getEncryptedRecoveryPhrase(e){if("string"!=typeof e)throw new Error("Password required");if(0===e.length)throw new Error("Password should not be empty");const t=await this.getRecoveryEntropy();if(!t)throw new Error("Recovery phrase reading failed");if(0===t.length)throw new Error("Recovery phrase reading failed");return S.encryptEntropy(t,e)}decryptEncryptedRecoveryPhrase(e,t){if(!ArrayBuffer.isView(e))throw new Error("Encrypted phrase should be an ArrayBuffer view");if(0===e.byteLength)throw new Error("Encrypted phrase should not be empty");if("string"!=typeof t)throw new Error("Password required");if(0===t.length)throw new Error("Password should not be empty");const i=S.decryptEntropy(e,t),s=E({entropy:i});return S.free(i),s}freeRecoveryPhrase(e){S.free(e)}async storeRecoveryPhrase(t){this.onlineBoot.opened||await this.onlineBoot.ready();const i=await e.from({mnemonic:t}),s=await this.local.get("@local/identity"),o=await this.local.get("@local/rooms",{roomKey:s.privateProfileKey});if(!v.equals(s.publicKey,i.identityPublicKey))throw new Error("Wrong recovery phrase");if(!o.identity.discoveryCoreKey){if(await this.internalCreateDiscoveryCore(i),this.discoveryCore.length>0){const e=Math.floor((Date.now()-1711547109544)/1e3);await this.discoveryCore.truncate(this.discoveryCore.length,e)}await this.discoveryCore.append({version:1,timestamp:Date.now(),key:o.roomKey,mailbox:o.capabilities.mailboxKeyPair.publicKey}),await this.privateProfile.dispatch("@keet-private-id/update-identity",{identity:{publicKey:i.identityPublicKey,discoveryCoreKey:this.discoveryCore.key}})}}async confirmRecoveryPhrase({persist:e=!1}={}){this.onlineBoot.opened||await this.onlineBoot.ready();const t=await this.internalEnterLocalTransaction();try{const i=await t.get("@local/identity");e||(i.entropy=null),i.status.isConfirmed=!0,await t.insert("@local/identity",i),await t.flush()}finally{await this.internalExitLocalTransaction()}this.emit("identity-changed")}async restoreIdentity(t,{create:i=!0}={}){this.onlineBoot.opened||await this.onlineBoot.ready();const[s]=await Promise.all([this.local.get("@local/identity"),e.from({mnemonic:t})]),o=f.discoveryKey(s.privateProfileKey),a=this.tasks.restoreIdentity(o,{mnemonic:t,recover:!1===i,profileDiscoveryTimeout:this._profileDiscoveryTimeout,buildType:this.buildType});await a.run();const r=await this.local.get("@local/identity"),n=await this.local.get("@local/rooms",{roomKey:r.privateProfileKey});this.privateProfile=await this.internalOpenPrivateProfile(),this.swarmWorker.startActiveSwarming(n.capabilities.swarmTopic)}async acceptJoinRequest(e,t){this.onlineBoot.opened||await this.onlineBoot.ready();const i=this.internalOpenExistingRoom(e);try{const s=await i.db.get("@keet/join-requests",{deviceKey:t});if(!s)return;const o=s.request;let a=null;if(o.identity){const i=await this.local.get("@local/identity");if(!v.equals(i.privateProfileKey,e))throw new Error("Accepting an identity device join request on a room that is not your private profile");a=Le(i,t,o.identity.devicePublicKey)}await i.dispatch("@keet/accept-join-request",{deviceKey:t,identity:a})}finally{this.internalReleaseRoom(i)}}async rejectJoinRequest(e,t){this.opened||await this.ready();const i=this.internalOpenExistingRoom(e);try{if(!await i.db.get("@keet/join-requests",{deviceKey:t}))throw new Error("Join request for that device key does not exist");await i.dispatch("@keet/reject-join-request",{deviceKey:t})}finally{this.internalReleaseRoom(i)}}async _sendContactRequest(e){this.onlineBoot.opened||await this.onlineBoot.ready();const t=await this.internalOpenPrivateProfile();try{this.tasks.sendContactRequest(e.memberKey,e).runBackground()}finally{this.internalReleaseRoom(t)}}async sendContactRequestFromContact(e,{message:t}){const i=(e=this.internalDecodeContactInfo(e)).attestation.memberKey,s=e.attestation.memberKey;return this._sendContactRequest({roomKey:i,memberKey:s,message:t,contactInfo:e})}async sendContactRequest(e,t,{message:i}){return this._sendContactRequest({roomKey:e,memberKey:t,message:i})}async addContact(t,i,{alias:s}={}){if(this.onlineBoot.opened||await this.onlineBoot.ready(),!i)throw me.INVALID_CONTACT_INFO();try{const{profile:o,attestation:a}=this.internalDecodeContactInfo(i),r=oe("@keet/member-profile",o),n=e.verify(a.proof,r,{expectedIdentity:t});if(!n?.receipt)throw me.INVALID_CONTACT_INFO();const l={memberKey:t,mailbox:o.mailbox,status:{isActive:!0,isBlocked:!1},contactInfo:{profile:o,attestation:a}};s&&(l.alias=s),await this.privateProfile.dispatch("@keet-private-id/add-contact",{contact:l})}catch(e){this.onerror(e)}}async setContactAlias(e,t){this.onlineBoot.opened||await this.onlineBoot.ready(),await this.privateProfile.dispatch("@keet-private-id/update-contact-alias",{memberKey:e,alias:t})}async removeContact(e){this.onlineBoot.opened||await this.onlineBoot.ready();const t=await this.privateProfile.db.get("@keet-private-id/contacts",{memberKey:e});t&&await this.privateProfile.dispatch("@keet-private-id/add-contact",{contact:{memberKey:e,mailbox:t.mailbox,status:{...t.status,isActive:!1},contactInfo:t.contactInfo}})}async blockContact(e){this.onlineBoot.opened||await this.onlineBoot.ready();const[t,i,s]=await Promise.all([this.privateProfile.db.findOne("@keet-private-id/contact-requests-by-member",{gte:{memberKey:e},lte:{memberKey:e}}),this.privateProfile.db.get("@keet-private-id/contacts",{memberKey:e}),this.local.get("@local/members",{key:e})]),o=i?.mailbox||s?.profile.mailbox||t?.sender?.mailbox;if(!o)throw new Error("Cannot find contact");const a={memberKey:e,mailbox:o,status:{...i?.status,isBlocked:!0},contactInfo:i?.contactInfo};await this.privateProfile.dispatch("@keet-private-id/add-contact",{contact:a})}async unblockContact(e){this.onlineBoot.opened||await this.onlineBoot.ready();const[t,i,s]=await Promise.all([this.privateProfile.db.findOne("@keet-private-id/contact-requests-by-member",{gte:{memberKey:e},lte:{memberKey:e}}),this.privateProfile.db.get("@keet-private-id/contacts",{memberKey:e}),this.local.get("@local/members",{key:e})]),o=i?.mailbox||s?.profile.mailbox||t?.sender?.mailbox;if(!o)throw new Error("Cannot find contact");const a={memberKey:e,mailbox:o,status:{...i?.status,isBlocked:!1},contactInfo:i?.contactInfo};await this.privateProfile.dispatch("@keet-private-id/add-contact",{contact:a})}async _flushDHTCache(){if(this.readOnly)return;const e=this.swarm.dht.toArray({limit:64});try{await this._setCachedDHTNodes(e)}catch(e){this.onerror(e)}}async getDHTSettings(){const e=await this.local.get("@local/hyperdht")||{};return{nodes:e.nodes||[],concurrency:e.concurrency||Z.concurrency,maxWindow:e.maxWindow||Z.maxWindow,randomPunchInterval:e.randomPunchInterval||Z.randomPunchInterval,connectionKeepAlive:e.connectionKeepAlive||Z.connectionKeepAlive}}async getSwarmSettings(){return{maxPeers:(await this.local.get("@local/hyperswarm")||{}).maxPeers||ee.maxPeers}}async _setCachedDHTNodes(e){const t=await this.internalEnterLocalTransaction();try{const i={...await this.getDHTSettings(),nodes:e};await t.insert("@local/hyperdht",i),await t.flush()}finally{await this.internalExitLocalTransaction()}}batch(e){return new Re(this,e)}async dispatch(e,t,i){this.opened||await this.ready();const s=this.internalOpenExistingRoom(e);try{return await s.dispatch(t,i)}catch(e){this.onerror(e)}finally{this.internalReleaseRoom(s)}}async dispatchBatch(e,t){this.opened||await this.ready();const i=this.internalOpenExistingRoom(e);try{await i.dispatchBatch(t)}finally{this.internalReleaseRoom(i)}}async selfDestruct({confirm:e}={}){if("do not go gentle into that good night"!==e)throw new Error("Must confirm self destruction");await this.ready(),await this.close();const t=Math.random().toString(16).slice(2),i=K.join(this.dir,"gc",t);try{await P.promises.mkdir(i,{recursive:!0})}catch{}for(const e of["cores","db","cache","app-references"])try{await P.promises.rename(K.join(this.dir,e),K.join(i,e))}catch{}await P.promises.rm(i,{recursive:!0})}_handleError(e,t={}){const i=this.errorManager.normalize(e,t);this.errorManager.add(i),this.logger.error(i),this._onerror(i)}static from(e,t){const i=C.migrate(e),s=i.active()||i.create({name:"default"});i.gc({delay:ke});const o=new this(s.storage,{...t,logout:async()=>{i.remove(s),await new Promise(e=>setTimeout(e,1e3)),await o.close(),i.gc({delay:ke})}});return{profiles:i,keet:o}}static registerRPC(e,t,i){return new B(e,t,i)}};