mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-09-09 09:35:55 +00:00
core: allow creating a user without making it active
CreateActiveUser gains keepActiveUser. Creating a user activates it, and APIChangePreparedContactUser resolves the chat under the active user, so create-then-reassign cannot find the chat the invitation is for. With the flag the sequence has nothing to undo: create, reassign while the old profile still owns it, switch once. BoolDef makes the field default to False, so older clients and both CLI parsers are unaffected. It is ignored when there is no active user to keep, which would otherwise leave none at all. Includes the regenerated NewUser type in the three generated client artifacts, and the two hand-synced clients that build a NewUser literal.
This commit is contained in:
@@ -3068,6 +3068,7 @@ SubscribeError:
|
||||
- pastTimestamp: bool
|
||||
- userChatRelay: bool
|
||||
- clientService: bool
|
||||
- keepActiveUser: bool
|
||||
|
||||
|
||||
---
|
||||
|
||||
@@ -3367,6 +3367,7 @@ export interface NewUser {
|
||||
pastTimestamp: boolean
|
||||
userChatRelay: boolean
|
||||
clientService: boolean
|
||||
keepActiveUser: boolean
|
||||
}
|
||||
|
||||
export interface NoteFolder {
|
||||
|
||||
@@ -867,7 +867,7 @@ export class ChatApi {
|
||||
* Network usage: no.
|
||||
*/
|
||||
async apiCreateActiveUser(profile?: T.Profile): Promise<T.User> {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -660,7 +660,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}))
|
||||
|
||||
@@ -2356,6 +2356,7 @@ class NewUser(TypedDict):
|
||||
pastTimestamp: bool
|
||||
userChatRelay: bool
|
||||
clientService: bool
|
||||
keepActiveUser: bool
|
||||
|
||||
class NoteFolder(TypedDict):
|
||||
noteFolderId: int # int64
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -420,7 +420,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
|
||||
@@ -432,17 +432,19 @@ 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
|
||||
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 ()
|
||||
@@ -5930,7 +5932,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
|
||||
@@ -5939,7 +5941,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
|
||||
|
||||
@@ -155,7 +155,8 @@ data NewUser = NewUser
|
||||
{ profile :: Maybe Profile,
|
||||
pastTimestamp :: Bool,
|
||||
userChatRelay :: BoolDef,
|
||||
clientService :: BoolDef
|
||||
clientService :: BoolDef,
|
||||
keepActiveUser :: BoolDef
|
||||
}
|
||||
deriving (Show)
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ chatProfileTests = do
|
||||
it "reject profile image that is too large" testSetProfileImageTooLarge
|
||||
it "set profile image from file" testSetProfileImageFromFile
|
||||
it "use multiword profile names" testMultiWordProfileNames
|
||||
it "create user without keepActiveUser activates it" testCreateUserWithoutKeepActiveUser
|
||||
it "auto-accept group invitations" testAutoAcceptGroupInvitations
|
||||
it "auto-accept group invitations on a second profile" testAutoAcceptGroupInvitationsSecondProfile
|
||||
it "auto-accept group invitations on an inactive profile" testAutoAcceptGroupInvitationsInactiveProfile
|
||||
@@ -156,6 +157,7 @@ shortLinkTests = do
|
||||
it "connect to prepared contact incognito (via address)" testShortLinkAddressConnectPreparedContactIncognito
|
||||
it "change prepared contact user" testShortLinkChangePreparedContactUser
|
||||
it "change prepared contact user, new user has contact with the same name" testShortLinkChangePreparedContactUserDuplicate
|
||||
it "create user keeping active user, then change prepared contact user" testCreateUserKeepingActiveUser
|
||||
it "connect to prepared group incognito" testShortLinkConnectPreparedGroupIncognito
|
||||
it "change prepared group user" testShortLinkChangePreparedGroupUser
|
||||
it "change prepared group user, new user has group with the same name" testShortLinkChangePreparedGroupUserDuplicate
|
||||
@@ -4050,6 +4052,38 @@ testShortLinkChangePreparedContactUser = testChat2 aliceProfile bobProfile test
|
||||
bob @@@ []
|
||||
bob `hasContactProfiles` ["bob"]
|
||||
|
||||
testCreateUserKeepingActiveUser :: HasCallStack => TestParams -> IO ()
|
||||
testCreateUserKeepingActiveUser = testChat2 aliceProfile bobProfile test
|
||||
where
|
||||
test alice bob = do
|
||||
bob ##> "/_create user {\"profile\":{\"displayName\":\"robert\",\"fullName\":\"\"},\"pastTimestamp\":false,\"keepActiveUser\":true}"
|
||||
showActiveUser bob "robert"
|
||||
bob ##> "/u"
|
||||
showActiveUser bob "bob (Bob)"
|
||||
bob ##> "/users"
|
||||
bob <## "bob (Bob) (active)"
|
||||
bob <## "robert"
|
||||
|
||||
alice ##> "/_connect 1"
|
||||
(shortLink, fullLink) <- getInvitations alice
|
||||
bob ##> ("/_connect plan 1 " <> shortLink)
|
||||
bob <## "invitation link: ok to connect"
|
||||
contactSLinkData <- getTermLine bob
|
||||
bob ##> ("/_prepare contact 1 " <> fullLink <> " " <> shortLink <> " " <> contactSLinkData)
|
||||
bob <## "alice: contact is prepared"
|
||||
|
||||
bob ##> "/_set contact user @4 2"
|
||||
bob <## "contact alice changed from user bob to user robert"
|
||||
|
||||
testCreateUserWithoutKeepActiveUser :: HasCallStack => TestParams -> IO ()
|
||||
testCreateUserWithoutKeepActiveUser = testChat aliceProfile test
|
||||
where
|
||||
test alice = do
|
||||
alice ##> "/_create user {\"profile\":{\"displayName\":\"alisa\",\"fullName\":\"\"},\"pastTimestamp\":false}"
|
||||
showActiveUser alice "alisa"
|
||||
alice ##> "/u"
|
||||
showActiveUser alice "alisa"
|
||||
|
||||
testShortLinkChangePreparedContactUserDuplicate :: HasCallStack => TestParams -> IO ()
|
||||
testShortLinkChangePreparedContactUserDuplicate = testChat2 aliceProfile bobProfile test
|
||||
where
|
||||
|
||||
Reference in New Issue
Block a user