diff --git a/bots/api/TYPES.md b/bots/api/TYPES.md index 4546a6aaa7..62eebd0e71 100644 --- a/bots/api/TYPES.md +++ b/bots/api/TYPES.md @@ -3054,6 +3054,7 @@ SubscribeError: - pastTimestamp: bool - userChatRelay: bool - clientService: bool +- keepActiveUser: bool --- diff --git a/packages/simplex-chat-client/types/typescript/src/types.ts b/packages/simplex-chat-client/types/typescript/src/types.ts index 5faf084dce..4b41a0e5db 100644 --- a/packages/simplex-chat-client/types/typescript/src/types.ts +++ b/packages/simplex-chat-client/types/typescript/src/types.ts @@ -3348,6 +3348,7 @@ export interface NewUser { pastTimestamp: boolean userChatRelay: boolean clientService: boolean + keepActiveUser: boolean } export interface NoteFolder { diff --git a/packages/simplex-chat-nodejs/src/api.ts b/packages/simplex-chat-nodejs/src/api.ts index 958304a8ea..069da85c12 100644 --- a/packages/simplex-chat-nodejs/src/api.ts +++ b/packages/simplex-chat-nodejs/src/api.ts @@ -867,7 +867,7 @@ export class ChatApi { * Network usage: no. */ async apiCreateActiveUser(profile?: T.Profile): Promise { - const r = await this.sendChatCmd(CC.CreateActiveUser.cmdString({newUser: {profile, pastTimestamp: false, userChatRelay: false, clientService: false}})) + const r = await this.sendChatCmd(CC.CreateActiveUser.cmdString({newUser: {profile, pastTimestamp: false, userChatRelay: false, clientService: false, keepActiveUser: false}})) if (r.type === "activeUser") return r.user throw new ChatCommandError("unexpected response", r) } diff --git a/packages/simplex-chat-python/src/simplex_chat/api.py b/packages/simplex-chat-python/src/simplex_chat/api.py index 51de063329..35bca24f53 100644 --- a/packages/simplex-chat-python/src/simplex_chat/api.py +++ b/packages/simplex-chat-python/src/simplex_chat/api.py @@ -642,7 +642,7 @@ class ChatApi: raise async def api_create_active_user(self, profile: T.Profile | None = None) -> T.User: - new_user: T.NewUser = {"pastTimestamp": False, "userChatRelay": False, "clientService": False} + new_user: T.NewUser = {"pastTimestamp": False, "userChatRelay": False, "clientService": False, "keepActiveUser": False} if profile is not None: new_user["profile"] = profile r = await self.send_chat_cmd(CC.CreateActiveUser_cmd_string({"newUser": new_user})) diff --git a/packages/simplex-chat-python/src/simplex_chat/types/_types.py b/packages/simplex-chat-python/src/simplex_chat/types/_types.py index b00a559f92..043c924edb 100644 --- a/packages/simplex-chat-python/src/simplex_chat/types/_types.py +++ b/packages/simplex-chat-python/src/simplex_chat/types/_types.py @@ -2337,6 +2337,7 @@ class NewUser(TypedDict): pastTimestamp: bool userChatRelay: bool clientService: bool + keepActiveUser: bool class NoteFolder(TypedDict): noteFolderId: int # int64 diff --git a/src/Simplex/Chat/Core.hs b/src/Simplex/Chat/Core.hs index c382a6dc8e..7b0ee24f95 100644 --- a/src/Simplex/Chat/Core.hs +++ b/src/Simplex/Chat/Core.hs @@ -160,7 +160,7 @@ createActiveUser cc CoreChatOpts {chatRelay, headless} createBot_ userDisplayNam createUser loop False $ mkProfile displayName mkProfile displayName = Profile {displayName, fullName = "", shortDescr = Nothing, description = Nothing, image = Nothing, contactLink = Nothing, peerType = Nothing, preferences = Nothing, badge = Nothing, contactDomain = Nothing} createUser onError clientService p = - execChatCommand' (CreateActiveUser NewUser {profile = Just p, pastTimestamp = False, userChatRelay = BoolDef chatRelay, clientService = BoolDef clientService}) 0 `runReaderT` cc >>= \case + execChatCommand' (CreateActiveUser NewUser {profile = Just p, pastTimestamp = False, userChatRelay = BoolDef chatRelay, clientService = BoolDef clientService, keepActiveUser = BoolDef False}) 0 `runReaderT` cc >>= \case Right (CRActiveUser user) -> pure user r -> printResponseEvent (Nothing, Nothing) (config cc) r >> onError diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 88cb4532ef..5cbb9e5a30 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -415,7 +415,7 @@ parseChatCommand = A.parseOnly chatCommandP . B.dropWhileEnd isSpace processChatCommand :: StoreCxt -> NetworkRequestMode -> ChatCommand -> CM ChatResponse processChatCommand cxt nm = \case ShowActiveUser -> withUser' $ pure . CRActiveUser - CreateActiveUser NewUser {profile, pastTimestamp, userChatRelay, clientService} -> do + CreateActiveUser NewUser {profile, pastTimestamp, userChatRelay, clientService, keepActiveUser} -> do forM_ profile $ \p@Profile {displayName, image} -> do checkValidName displayName checkProfileImageSize image @@ -427,17 +427,21 @@ processChatCommand cxt nm = \case when (n == displayName) . throwChatError $ if activeUser || isNothing viewPwdHash then CEUserExists displayName else CEInvalidDisplayName {displayName, validName = ""} when (isTrue userChatRelay && isTrue userChatRelay') $ throwChatError CEChatRelayExists - (uss, (smp', xftp')) <- chooseServers =<< readTVarIO u + curUser_ <- readTVarIO u + (uss, (smp', xftp')) <- chooseServers curUser_ let service = isTrue clientService + -- keepActiveUser can only be honoured when there is a user to keep, + -- otherwise it would leave no active user at all. + activateNewUser = isNothing curUser_ || not (isTrue keepActiveUser) auId <- withAgent $ \a -> createUser a service smp' xftp' ts <- liftIO $ getCurrentTime >>= if pastTimestamp then coupleDaysAgo else pure user <- withFastStore $ \db -> do - user <- createUserRecordAt db (AgentUserId auId) (isTrue userChatRelay) service p True ts + user <- createUserRecordAt db (AgentUserId auId) (isTrue userChatRelay) service p activateNewUser ts mapM_ (setUserServers db user ts) uss createPresetContactCards db user `catchAllErrors` \_ -> pure () createNoteFolder db user pure user - atomically . writeTVar u $ Just user + when activateNewUser $ atomically . writeTVar u $ Just user pure $ CRActiveUser user where createPresetContactCards :: DB.Connection -> User -> ExceptT StoreError IO () @@ -5907,7 +5911,7 @@ chatCommandP = (cName, shortDescr) <- profileNameDescr service <- (" service=" *> onOffP) <|> pure False let profile = Just Profile {displayName = cName, fullName = "", shortDescr, description = Nothing, image = Nothing, contactLink = Nothing, peerType = Nothing, preferences = Nothing, badge = Nothing, contactDomain = Nothing} - pure NewUser {profile, pastTimestamp = False, userChatRelay = BoolDef relay, clientService = BoolDef service} + pure NewUser {profile, pastTimestamp = False, userChatRelay = BoolDef relay, clientService = BoolDef service, keepActiveUser = BoolDef False} newBotUserP = do files_ <- optional $ "files=" *> onOffP <* A.space service <- ("service=" *> onOffP <* A.space) <|> pure False @@ -5916,7 +5920,7 @@ chatCommandP = Just True -> Nothing _ -> Just (emptyChatPrefs :: Preferences) {files = Just FilesPreference {allow = FANo}} profile = Just Profile {displayName = cName, fullName = "", shortDescr, description = Nothing, image = Nothing, contactLink = Nothing, peerType = Just CPTBot, preferences, badge = Nothing, contactDomain = Nothing} - pure NewUser {profile, pastTimestamp = False, userChatRelay = BoolDef False, clientService = BoolDef service} + pure NewUser {profile, pastTimestamp = False, userChatRelay = BoolDef False, clientService = BoolDef service, keepActiveUser = BoolDef False} jsonP :: J.FromJSON a => Parser a jsonP = J.eitherDecodeStrict' <$?> A.takeByteString groupProfile = do diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index 7bccd04b71..8645ea7ab4 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -154,7 +154,11 @@ data NewUser = NewUser { profile :: Maybe Profile, pastTimestamp :: Bool, userChatRelay :: BoolDef, - clientService :: BoolDef + clientService :: BoolDef, + -- when set, the user is created without becoming active, preserving the current + -- one; absent/False activates it as before. The response is CRActiveUser either + -- way - it carries the created user, which is then not the active one. + keepActiveUser :: BoolDef } deriving (Show)