update name in bot address

This commit is contained in:
Evgeny @ SimpleX Chat
2026-09-25 21:20:48 +00:00
parent aa989f61c9
commit f9c8049dc2
11 changed files with 199 additions and 13 deletions
+12 -1
View File
@@ -422,7 +422,18 @@ export class ChatApi {
const r = await this.sendChatCmd(CC.APISetAddressSettings.cmdString({userId, settings}))
if (r.type !== "userContactLinkUpdated") {
throw new ChatCommandError("error changing user contact address settings", r)
}
}
}
async apiSetUserDomain(userId: number, simplexDomain?: string): Promise<T.User> {
const r = await this.sendChatCmd(CC.APISetUserDomain.cmdString({userId, simplexDomain}))
switch (r.type) {
case "userProfileUpdated":
case "userProfileNoChange":
return r.user
default:
throw new ChatCommandError("error setting SimpleX name", r)
}
}
/**
+24 -5
View File
@@ -35,6 +35,7 @@ const defaultOpts: Required<BotOptions> = {
export interface BotConfig {
profile: T.Profile,
simplexName?: string,
dbOpts: BotDbOpts,
options: BotOptions,
onMessage?: (chatItem: T.AChatItem, content: T.MsgContent, chat: api.ChatApi) => void | Promise<void>,
@@ -45,7 +46,7 @@ export interface BotConfig {
events?: api.EventSubscribers
}
export async function run({profile, dbOpts, options = defaultOpts, onMessage, onCommands = {}, events = {}}: BotConfig): Promise<[api.ChatApi, T.User, T.UserContactLink | undefined]> {
export async function run({profile, simplexName, dbOpts, options = defaultOpts, onMessage, onCommands = {}, events = {}}: BotConfig): Promise<[api.ChatApi, T.User, T.UserContactLink | undefined]> {
const bot = await api.ChatApi.init(dbOpts, dbOpts.confirmMigrations || core.MigrationConfirmation.YesUp, dbOpts.queueSize)
const opts = fullOptions(options)
if (onMessage || Object.keys(onCommands).length > 0) subscribeChatItems(bot, onMessage, onCommands)
@@ -60,7 +61,8 @@ export async function run({profile, dbOpts, options = defaultOpts, onMessage, on
console.log(`Bot address: ${addressLink}`)
if (opts.useBotProfile) botProfile.contactLink = addressLink
}
await updateBotUserProfile(bot, user, botProfile, opts)
const namedUser = await updateBotSimplexName(bot, user, simplexName, opts)
await updateBotUserProfile(bot, namedUser, botProfile, opts)
return [bot, user, address]
}
@@ -180,15 +182,32 @@ async function createOrUpdateAddress(bot: api.ChatApi, user: T.User, opts: Requi
}
}
return address
return address
}
async function updateBotSimplexName(bot: api.ChatApi, user: T.User, simplexName: string | undefined, opts: Required<BotOptions>): Promise<T.User> {
const name = simplexName?.toLowerCase()
if (user.profile.contactDomain?.domain === name) return user
if (!opts.updateAddress) {
console.log("Bot SimpleX name changed")
return user
}
console.log("Bot SimpleX name changed, updating...")
try {
return await bot.apiSetUserDomain(user.userId, name)
} catch (e) {
console.log("Error updating bot SimpleX name", e)
return user
}
}
async function updateBotUserProfile(bot: api.ChatApi, user: T.User, profile: T.Profile, opts: Required<BotOptions>): Promise<void> {
const {userId} = user
if (!equal(util.fromLocalProfile(user.profile), profile)) {
const {contactDomain, ...currentProfile} = util.fromLocalProfile(user.profile)
if (!equal(currentProfile, profile)) {
if (opts.updateProfile) {
console.log("Bot profile changed, updating...")
const summary = await bot.apiUpdateProfile(userId, profile)
const summary = await bot.apiUpdateProfile(userId, {...profile, contactDomain})
console.log(
summary
? `Bot profile updated: ${summary.updateSuccesses} updated contact(s), ${summary.updateFailures} failed contact update(s).`
+2 -2
View File
@@ -53,8 +53,8 @@ export function botAddressSettings({addressSettings}: T.UserContactLink): BotAdd
}
}
export function fromLocalProfile({displayName, fullName, shortDescr, image, contactLink, preferences, peerType}: T.LocalProfile): T.Profile {
const profile = {displayName, fullName, shortDescr, image, contactLink, preferences, peerType}
export function fromLocalProfile({displayName, fullName, shortDescr, image, contactLink, preferences, peerType, contactDomain}: T.LocalProfile): T.Profile {
const profile = {displayName, fullName, shortDescr, image, contactLink, preferences, peerType, contactDomain: contactDomain && {domain: contactDomain.domain}}
for (const key in profile) {
if (typeof (profile as any)[key] === "undefined") delete (profile as any)[key]
}
@@ -30,6 +30,13 @@ describe("documented success responses", () => {
await expect(chat.apiSetProfileAddress(1, true)).resolves.toEqual({updateSuccesses: 0, updateFailures: 0, changedContacts: []})
})
it("apiSetUserDomain sets and removes the name", async () => {
const chat = await chatWithResponse({type: "userProfileNoChange", user})
await expect(chat.apiSetUserDomain(1, "calc.simplex")).resolves.toEqual(user)
await expect(chat.apiSetUserDomain(1)).resolves.toEqual(user)
expect(jest.mocked(core.chatSendCmd).mock.calls.map(([, cmd]) => cmd)).toEqual(["/_set domain 1 calc.simplex", "/_set domain 1"])
})
it("apiReceiveFile reports a file cancelled by sender", async () => {
const chat = await chatWithResponse({type: "rcvFileAcceptedSndCancelled", user, rcvFileTransfer: {}})
await expect(chat.apiReceiveFile(3)).rejects.toThrow("file cancelled by sender")
@@ -1,6 +1,6 @@
import {ChatEvent, T} from "@simplex-chat/types"
import * as api from "../src/api"
import {subscribeChatItems} from "../src/bot"
import {run, subscribeChatItems} from "../src/bot"
type Handler = (evt: ChatEvent) => Promise<void>
@@ -56,3 +56,68 @@ describe("subscribeChatItems", () => {
expect(calls).toEqual([])
})
})
describe("run", () => {
const address = {
connLinkContact: {connFullLink: "full", connShortLink: "short"},
addressSettings: {businessAddress: false, autoAccept: {acceptIncognito: false}},
} as unknown as T.UserContactLink
function fakeChat(contactDomain?: T.SimplexDomainClaim) {
const user = {userId: 1, profile: {displayName: "Old", fullName: "", contactDomain}} as unknown as T.User
const chat = {
on: jest.fn(),
apiGetActiveUser: jest.fn().mockResolvedValue(user),
startChat: jest.fn(),
apiGetUserAddress: jest.fn().mockResolvedValue(address),
apiSetAddressSettings: jest.fn(),
apiSetUserDomain: jest.fn(async (_userId: number, domain?: string) => ({...user, profile: {...user.profile, contactDomain: domain && {domain}}})),
apiUpdateProfile: jest.fn().mockResolvedValue({updateSuccesses: 0, updateFailures: 0}),
}
jest.spyOn(api.ChatApi, "init").mockResolvedValue(chat as unknown as api.ChatApi)
return chat
}
const runBot = (simplexName?: string, options = {}) =>
run({profile: {displayName: "Calculator", fullName: ""}, simplexName, dbOpts: {type: "sqlite", filePrefix: "unused"}, options})
const updatedProfile = (chat: ReturnType<typeof fakeChat>) => chat.apiUpdateProfile.mock.calls[0][1]
beforeEach(() => jest.spyOn(console, "log").mockImplementation(() => {}))
afterEach(() => jest.restoreAllMocks())
it("sets the configured SimpleX name", async () => {
const chat = fakeChat()
await runBot("Calc.simplex")
expect(chat.apiSetUserDomain).toHaveBeenCalledWith(1, "calc.simplex")
expect(updatedProfile(chat).contactDomain).toEqual({domain: "calc.simplex"})
})
it("removes the SimpleX name that is not configured", async () => {
const chat = fakeChat({domain: "calc.simplex"})
await runBot()
expect(chat.apiSetUserDomain).toHaveBeenCalledWith(1, undefined)
expect(updatedProfile(chat).contactDomain).toBeUndefined()
})
it("keeps the SimpleX name when updating the profile", async () => {
const chat = fakeChat({domain: "calc.simplex", proof: {presHeader: "header", signature: "signature"}})
await runBot("calc.simplex")
expect(chat.apiSetUserDomain).not.toHaveBeenCalled()
expect(updatedProfile(chat).displayName).toBe("Calculator")
expect(updatedProfile(chat).contactDomain).toEqual({domain: "calc.simplex"})
})
it("continues when the SimpleX name cannot be set", async () => {
const chat = fakeChat()
chat.apiSetUserDomain.mockRejectedValue(new Error("simplexDomainNotReady"))
await expect(runBot("calc.simplex")).resolves.toBeDefined()
expect(updatedProfile(chat).contactDomain).toBeUndefined()
})
it("does not change the SimpleX name without updateAddress", async () => {
const chat = fakeChat()
await runBot("calc.simplex", {updateAddress: false})
expect(chat.apiSetUserDomain).not.toHaveBeenCalled()
})
})