simplex-chat-nodejs: add member contact API methods (#6763)

* simplex-chat-nodejs: add apiCreateMemberContact and apiSendMemberContactInvitation

* simplex-chat-nodejs: add integration test for apiCreateMemberContact and apiSendMemberContactInvitation

Test creates a 3-user group with direct messages enabled, then verifies:
- apiCreateMemberContact creates a DM contact between group members
- apiSendMemberContactInvitation sends an invitation that the recipient receives

* simplex-chat-nodejs: bump @simplex-chat/types to ^0.4.0
This commit is contained in:
Narasimha-sc
2026-04-10 08:37:37 +00:00
committed by GitHub
parent e6dde90c40
commit e2ecff7215
3 changed files with 116 additions and 1 deletions

View File

@@ -24,7 +24,7 @@
"docs": "typedoc"
},
"dependencies": {
"@simplex-chat/types": "^0.3.0",
"@simplex-chat/types": "^0.4.0",
"extract-zip": "^2.0.1",
"fast-deep-equal": "^3.1.3",
"node-addon-api": "^8.5.0"

View File

@@ -872,4 +872,34 @@ export class ChatApi {
const r = await this.sendChatCmd(CC.APISetContactPrefs.cmdString({contactId, preferences}))
if (r.type !== "contactPrefsUpdated") throw new ChatCommandError("error setting contact prefs", r)
}
/**
* Create a direct message contact with a group member.
* Returns the created contact.
* Network usage: interactive.
*/
async apiCreateMemberContact(groupId: number, groupMemberId: number): Promise<T.Contact> {
const r: any = await this.sendChatCmd(`/_create member contact #${groupId} ${groupMemberId}`)
if (r.type === "newMemberContact") return r.contact
throw new ChatCommandError("error creating member contact", r)
}
/**
* Send a direct message invitation to a group member contact.
* The contact must have been created with {@link apiCreateMemberContact}.
* Network usage: interactive.
*/
async apiSendMemberContactInvitation(contactId: number, message?: T.MsgContent | string): Promise<T.Contact> {
let cmd = `/_invite member contact @${contactId}`
if (message !== undefined) {
if (typeof message === "string") {
cmd += ` text ${message}`
} else {
cmd += ` json ${JSON.stringify(message)}`
}
}
const r: any = await this.sendChatCmd(cmd)
if (r.type === "newMemberContactSentInv") return r.contact
throw new ChatCommandError("error sending member contact invitation", r)
}
}

View File

@@ -64,4 +64,89 @@ describe("API tests (use preset servers)", () => {
expect(servers[0] !== servers[1]).toBe(true)
expect(eventCount > 0).toBe(true)
}, 30000)
it("should create member contact and send invitation", async () => {
// create 3 users and start chat controllers
const alice = await api.ChatApi.init(alicePath)
const bob = await api.ChatApi.init(bobPath)
const carolPath = path.join(tmpDir, "carol")
const carol = await api.ChatApi.init(carolPath)
const aliceUser = await alice.apiCreateActiveUser({displayName: "alice", fullName: ""})
await bob.apiCreateActiveUser({displayName: "bob", fullName: ""})
await carol.apiCreateActiveUser({displayName: "carol", fullName: ""})
await alice.startChat()
await bob.startChat()
await carol.startChat()
// connect alice <-> bob
const aliceLink1 = await alice.apiCreateLink(aliceUser.userId)
await expect(bob.apiConnectActiveUser(aliceLink1)).resolves.toBe(api.ConnReqType.Invitation)
const [bobContact] = await Promise.all([
(await alice.wait("contactConnected")).contact,
(await bob.wait("contactConnected")).contact
])
// connect alice <-> carol
const aliceLink2 = await alice.apiCreateLink(aliceUser.userId)
await expect(carol.apiConnectActiveUser(aliceLink2)).resolves.toBe(api.ConnReqType.Invitation)
const [carolContact] = await Promise.all([
(await alice.wait("contactConnected")).contact,
(await carol.wait("contactConnected")).contact
])
// create group with direct messages enabled
const group = await alice.apiNewGroup(aliceUser.userId, {
displayName: "test-group",
fullName: "",
groupPreferences: {
directMessages: {enable: T.GroupFeatureEnabled.On},
},
})
const groupId = group.groupId
// add bob to the group
const bobInvP = bob.wait("receivedGroupInvitation", 15000)
await alice.apiAddMember(groupId, bobContact.contactId, T.GroupMemberRole.Member)
const bobInvEvt = await bobInvP
expect(bobInvEvt).toBeDefined()
const aliceBobConnP = alice.wait("connectedToGroupMember", 15000)
const bobAliceConnP = bob.wait("connectedToGroupMember", 15000)
await bob.apiJoinGroup(bobInvEvt!.groupInfo.groupId)
await Promise.all([aliceBobConnP, bobAliceConnP])
// add carol to the group
const carolInvP = carol.wait("receivedGroupInvitation", 30000)
await alice.apiAddMember(groupId, carolContact.contactId, T.GroupMemberRole.Member)
const carolInvEvt = await carolInvP
expect(carolInvEvt).toBeDefined()
// wait for carol to connect to both alice and bob (and vice versa)
const bobCarolConnP = bob.wait("connectedToGroupMember",
(evt: CEvt.ConnectedToGroupMember) => evt.member.memberProfile.displayName === "carol", 30000)
const carolAliceConnP = carol.wait("connectedToGroupMember",
(evt: CEvt.ConnectedToGroupMember) => evt.member.memberProfile.displayName === "alice", 30000)
const carolBobConnP = carol.wait("connectedToGroupMember",
(evt: CEvt.ConnectedToGroupMember) => evt.member.memberProfile.displayName === "bob", 30000)
const aliceCarolConnP = alice.wait("connectedToGroupMember",
(evt: CEvt.ConnectedToGroupMember) => evt.member.memberProfile.displayName === "carol", 30000)
await carol.apiJoinGroup(carolInvEvt!.groupInfo.groupId)
await Promise.all([bobCarolConnP, carolAliceConnP, carolBobConnP, aliceCarolConnP])
// find carol's memberId from bob's perspective
const members = await bob.apiListMembers(groupId)
const carolMember = members.find(m => m.memberProfile.displayName === "carol")
expect(carolMember).toBeDefined()
// test apiCreateMemberContact
const dmContact = await bob.apiCreateMemberContact(groupId, carolMember!.groupMemberId)
expect(dmContact).toBeDefined()
expect(dmContact.contactId).toBeDefined()
// test apiSendMemberContactInvitation
const carolDmP = carol.wait("newMemberContactReceivedInv" as CEvt.Tag, 30000)
const invContact = await bob.apiSendMemberContactInvitation(dmContact.contactId, "hello from bob")
expect(invContact).toBeDefined()
// carol should receive the member contact invitation
const carolDmEvt = await carolDmP
expect(carolDmEvt).toBeDefined()
expect((carolDmEvt as any).contact).toBeDefined()
// cleanup
await alice.stopChat()
await bob.stopChat()
await carol.stopChat()
await alice.close()
await bob.close()
await carol.close()
}, 90000)
})