mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-05-15 23:16:18 +00:00
Merge branch 'master' into f/channel-comments
This commit is contained in:
@@ -42,7 +42,7 @@ struct GroupProfileView: View {
|
||||
|
||||
Section {
|
||||
HStack {
|
||||
TextField("Group display name", text: $groupProfile.displayName)
|
||||
TextField(groupInfo.useRelays ? "Channel display name" : "Group display name", text: $groupProfile.displayName)
|
||||
.focused($focusDisplayName)
|
||||
if !validNewProfileName {
|
||||
Button {
|
||||
@@ -54,7 +54,7 @@ struct GroupProfileView: View {
|
||||
}
|
||||
let fullName = groupInfo.groupProfile.fullName
|
||||
if fullName != "" && fullName != groupProfile.displayName {
|
||||
TextField("Group full name (optional)", text: $groupProfile.fullName)
|
||||
TextField(groupInfo.useRelays ? "Channel full name (optional)" : "Group full name (optional)", text: $groupProfile.fullName)
|
||||
}
|
||||
HStack {
|
||||
TextField("Short description", text: $shortDescr)
|
||||
@@ -67,7 +67,7 @@ struct GroupProfileView: View {
|
||||
}
|
||||
}
|
||||
} footer: {
|
||||
Text("Group profile is stored on members' devices, not on the servers.")
|
||||
Text(groupInfo.useRelays ? "Channel profile is stored on subscribers' devices and on the chat relays." : "Group profile is stored on members' devices, not on the servers.")
|
||||
}
|
||||
|
||||
Section {
|
||||
@@ -80,11 +80,11 @@ struct GroupProfileView: View {
|
||||
currentProfileHash == groupProfile.hashValue &&
|
||||
(groupInfo.groupProfile.shortDescr ?? "") == shortDescr.trimmingCharacters(in: .whitespaces)
|
||||
)
|
||||
Button("Save group profile", action: saveProfile)
|
||||
Button(groupInfo.useRelays ? "Save channel profile" : "Save group profile", action: saveProfile)
|
||||
.disabled(!canUpdateProfile)
|
||||
}
|
||||
}
|
||||
.confirmationDialog("Group image", isPresented: $showChooseSource, titleVisibility: .visible) {
|
||||
.confirmationDialog(groupInfo.useRelays ? "Channel image" : "Group image", isPresented: $showChooseSource, titleVisibility: .visible) {
|
||||
Button("Take picture") {
|
||||
showTakePhoto = true
|
||||
}
|
||||
@@ -130,9 +130,15 @@ struct GroupProfileView: View {
|
||||
.onDisappear {
|
||||
if canUpdateProfile {
|
||||
showAlert(
|
||||
title: NSLocalizedString("Save group profile?", comment: "alert title"),
|
||||
message: NSLocalizedString("Group profile was changed. If you save it, the updated profile will be sent to group members.", comment: "alert message"),
|
||||
buttonTitle: NSLocalizedString("Save (and notify members)", comment: "alert button"),
|
||||
title: groupInfo.useRelays
|
||||
? NSLocalizedString("Save channel profile?", comment: "alert title")
|
||||
: NSLocalizedString("Save group profile?", comment: "alert title"),
|
||||
message: groupInfo.useRelays
|
||||
? NSLocalizedString("Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers.", comment: "alert message")
|
||||
: NSLocalizedString("Group profile was changed. If you save it, the updated profile will be sent to group members.", comment: "alert message"),
|
||||
buttonTitle: groupInfo.useRelays
|
||||
? NSLocalizedString("Save (and notify subscribers)", comment: "alert button")
|
||||
: NSLocalizedString("Save (and notify members)", comment: "alert button"),
|
||||
buttonAction: saveProfile,
|
||||
cancelButton: true
|
||||
)
|
||||
@@ -142,14 +148,14 @@ struct GroupProfileView: View {
|
||||
switch a {
|
||||
case let .saveError(err):
|
||||
return Alert(
|
||||
title: Text("Error saving group profile"),
|
||||
title: Text(groupInfo.useRelays ? "Error saving channel profile" : "Error saving group profile"),
|
||||
message: Text(err)
|
||||
)
|
||||
case let .invalidName(name):
|
||||
return createInvalidNameAlert(name, $groupProfile.displayName)
|
||||
}
|
||||
}
|
||||
.navigationBarTitle("Group profile")
|
||||
.navigationBarTitle(groupInfo.useRelays ? "Channel profile" : "Group profile")
|
||||
.modifier(ThemedBackground(grouped: true))
|
||||
.navigationBarTitleDisplayMode(focusDisplayName ? .inline : .large)
|
||||
}
|
||||
|
||||
+4
-3
@@ -2217,18 +2217,19 @@ object ChatController {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
suspend fun apiUpdateGroup(rh: Long?, groupId: Long, groupProfile: GroupProfile): GroupInfo? {
|
||||
suspend fun apiUpdateGroup(rh: Long?, groupId: Long, groupProfile: GroupProfile, isChannel: Boolean): GroupInfo? {
|
||||
val r = sendCmd(rh, CC.ApiUpdateGroupProfile(groupId, groupProfile))
|
||||
val errorTitle = if (isChannel) MR.strings.error_saving_channel_profile else MR.strings.error_saving_group_profile
|
||||
return when {
|
||||
r is API.Result && r.res is CR.GroupUpdated -> r.res.toGroup
|
||||
r is API.Error -> {
|
||||
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_saving_group_profile), "$r.err")
|
||||
AlertManager.shared.showAlertMsg(generalGetString(errorTitle), "$r.err")
|
||||
null
|
||||
}
|
||||
else -> {
|
||||
Log.e(TAG, "apiUpdateGroup bad response: ${r.responseType} ${r.details}")
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(MR.strings.error_saving_group_profile),
|
||||
generalGetString(errorTitle),
|
||||
"${r.responseType}: ${r.details}"
|
||||
)
|
||||
null
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ fun GroupPreferencesView(m: ChatModel, rhId: Long?, chatId: String, close: () ->
|
||||
fun savePrefs(afterSave: () -> Unit = {}) {
|
||||
withBGApi {
|
||||
val gp = gInfo.groupProfile.copy(groupPreferences = preferences.toGroupPreferences())
|
||||
val g = m.controller.apiUpdateGroup(rhId, gInfo.groupId, gp)
|
||||
val g = m.controller.apiUpdateGroup(rhId, gInfo.groupId, gp, gInfo.useRelays)
|
||||
if (g != null) {
|
||||
withContext(Dispatchers.Main) {
|
||||
chatModel.chatsContext.updateGroup(rhId, g)
|
||||
|
||||
+18
-9
@@ -32,10 +32,11 @@ import java.net.URI
|
||||
fun GroupProfileView(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel, close: () -> Unit) {
|
||||
GroupProfileLayout(
|
||||
close = close,
|
||||
groupInfo = groupInfo,
|
||||
groupProfile = groupInfo.groupProfile,
|
||||
saveProfile = { p ->
|
||||
withBGApi {
|
||||
val gInfo = chatModel.controller.apiUpdateGroup(rhId, groupInfo.groupId, p)
|
||||
val gInfo = chatModel.controller.apiUpdateGroup(rhId, groupInfo.groupId, p, groupInfo.useRelays)
|
||||
if (gInfo != null) {
|
||||
withContext(Dispatchers.Main) {
|
||||
chatModel.chatsContext.updateGroup(rhId, gInfo)
|
||||
@@ -50,9 +51,11 @@ fun GroupProfileView(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel, cl
|
||||
@Composable
|
||||
fun GroupProfileLayout(
|
||||
close: () -> Unit,
|
||||
groupInfo: GroupInfo,
|
||||
groupProfile: GroupProfile,
|
||||
saveProfile: (GroupProfile) -> Unit,
|
||||
) {
|
||||
val isChannel = groupInfo.useRelays
|
||||
val bottomSheetModalState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden)
|
||||
val displayName = rememberSaveable { mutableStateOf(groupProfile.displayName) }
|
||||
val fullName = rememberSaveable { mutableStateOf(groupProfile.fullName) }
|
||||
@@ -71,7 +74,7 @@ fun GroupProfileLayout(
|
||||
if (dataUnchanged || !canUpdateProfile(displayName.value, shortDescr.value, groupProfile)) {
|
||||
close()
|
||||
} else {
|
||||
showUnsavedChangesAlert({
|
||||
showUnsavedChangesAlert(isChannel, {
|
||||
saveProfile(
|
||||
groupProfile.copy(
|
||||
displayName = displayName.value.trim(),
|
||||
@@ -103,7 +106,11 @@ fun GroupProfileLayout(
|
||||
Modifier.fillMaxWidth()
|
||||
.padding(horizontal = DEFAULT_PADDING)
|
||||
) {
|
||||
ReadableText(MR.strings.group_profile_is_stored_on_members_devices, TextAlign.Center)
|
||||
ReadableText(
|
||||
if (isChannel) MR.strings.channel_profile_is_stored_on_subscribers_devices
|
||||
else MR.strings.group_profile_is_stored_on_members_devices,
|
||||
TextAlign.Center
|
||||
)
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -122,7 +129,7 @@ fun GroupProfileLayout(
|
||||
}
|
||||
Row(Modifier.padding(bottom = DEFAULT_PADDING_HALF).fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||
Text(
|
||||
stringResource(MR.strings.group_display_name_field),
|
||||
stringResource(if (isChannel) MR.strings.channel_display_name_field else MR.strings.group_display_name_field),
|
||||
fontSize = 16.sp
|
||||
)
|
||||
if (!isValidNewProfileName(displayName.value, groupProfile)) {
|
||||
@@ -136,7 +143,7 @@ fun GroupProfileLayout(
|
||||
if (groupProfile.fullName.trim().isNotEmpty() && groupProfile.fullName.trim() != groupProfile.displayName.trim()) {
|
||||
Spacer(Modifier.height(DEFAULT_PADDING))
|
||||
Text(
|
||||
stringResource(MR.strings.group_full_name_field),
|
||||
stringResource(if (isChannel) MR.strings.channel_full_name_field else MR.strings.group_full_name_field),
|
||||
fontSize = 16.sp,
|
||||
modifier = Modifier.padding(bottom = DEFAULT_PADDING_HALF)
|
||||
)
|
||||
@@ -164,9 +171,10 @@ fun GroupProfileLayout(
|
||||
|
||||
Spacer(Modifier.height(DEFAULT_PADDING))
|
||||
val enabled = !dataUnchanged && canUpdateProfile(displayName.value, shortDescr.value, groupProfile)
|
||||
val saveProfileLabel = if (isChannel) MR.strings.save_channel_profile else MR.strings.save_group_profile
|
||||
if (enabled) {
|
||||
Text(
|
||||
stringResource(MR.strings.save_group_profile),
|
||||
stringResource(saveProfileLabel),
|
||||
modifier = Modifier.clickable {
|
||||
saveProfile(
|
||||
groupProfile.copy(
|
||||
@@ -181,7 +189,7 @@ fun GroupProfileLayout(
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
stringResource(MR.strings.save_group_profile),
|
||||
stringResource(saveProfileLabel),
|
||||
color = MaterialTheme.colors.secondary
|
||||
)
|
||||
}
|
||||
@@ -204,10 +212,10 @@ private fun canUpdateProfile(displayName: String, shortDescr: String, groupProfi
|
||||
private fun isValidNewProfileName(displayName: String, groupProfile: GroupProfile): Boolean =
|
||||
displayName == groupProfile.displayName || isValidDisplayName(displayName.trim())
|
||||
|
||||
private fun showUnsavedChangesAlert(save: () -> Unit, revert: () -> Unit) {
|
||||
private fun showUnsavedChangesAlert(isChannel: Boolean, save: () -> Unit, revert: () -> Unit) {
|
||||
AlertManager.shared.showAlertDialogStacked(
|
||||
title = generalGetString(MR.strings.save_preferences_question),
|
||||
confirmText = generalGetString(MR.strings.save_and_notify_group_members),
|
||||
confirmText = generalGetString(if (isChannel) MR.strings.save_and_notify_channel_subscribers else MR.strings.save_and_notify_group_members),
|
||||
dismissText = generalGetString(MR.strings.exit_without_saving),
|
||||
onConfirm = save,
|
||||
onDismiss = revert,
|
||||
@@ -224,6 +232,7 @@ fun PreviewGroupProfileLayout() {
|
||||
SimpleXTheme {
|
||||
GroupProfileLayout(
|
||||
close = {},
|
||||
groupInfo = GroupInfo.sampleData,
|
||||
groupProfile = GroupProfile.sampleData,
|
||||
saveProfile = { _ -> }
|
||||
)
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ fun MemberAdmissionView(m: ChatModel, rhId: Long?, chatId: String, close: () ->
|
||||
fun saveAdmission(afterSave: () -> Unit = {}) {
|
||||
withBGApi {
|
||||
val gp = gInfo.groupProfile.copy(memberAdmission = admission)
|
||||
val g = m.controller.apiUpdateGroup(rhId, gInfo.groupId, gp)
|
||||
val g = m.controller.apiUpdateGroup(rhId, gInfo.groupId, gp, gInfo.useRelays)
|
||||
if (g != null) {
|
||||
withContext(Dispatchers.Main) {
|
||||
chatModel.chatsContext.updateGroup(rhId, g)
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ fun GroupWelcomeView(m: ChatModel, rhId: Long?, groupInfo: GroupInfo, close: ()
|
||||
welcome = null
|
||||
}
|
||||
val groupProfileUpdated = gInfo.groupProfile.copy(description = welcome)
|
||||
val res = m.controller.apiUpdateGroup(rhId, gInfo.groupId, groupProfileUpdated)
|
||||
val res = m.controller.apiUpdateGroup(rhId, gInfo.groupId, groupProfileUpdated, gInfo.useRelays)
|
||||
if (res != null) {
|
||||
gInfo = res
|
||||
withContext(Dispatchers.Main) {
|
||||
|
||||
@@ -1171,6 +1171,7 @@
|
||||
<string name="save_and_notify_contact">Save and notify contact</string>
|
||||
<string name="save_and_notify_contacts">Save and notify contacts</string>
|
||||
<string name="save_and_notify_group_members">Save and notify group members</string>
|
||||
<string name="save_and_notify_channel_subscribers">Save and notify channel subscribers</string>
|
||||
<string name="exit_without_saving">Exit without saving</string>
|
||||
|
||||
<!-- HiddenProfileView.kt -->
|
||||
@@ -1999,6 +2000,7 @@
|
||||
<string name="group_is_decentralized">Fully decentralized – visible only to members.</string>
|
||||
<string name="group_display_name_field">Enter group name:</string>
|
||||
<string name="group_full_name_field">Group full name:</string>
|
||||
<string name="channel_full_name_field">Channel full name:</string>
|
||||
<string name="group_short_descr_field">Short description:</string>
|
||||
<string name="group_descr_too_large">Description too large</string>
|
||||
<string name="group_main_profile_sent">Your chat profile will be sent to group members</string>
|
||||
@@ -2007,8 +2009,11 @@
|
||||
|
||||
<!-- GroupProfileView.kt -->
|
||||
<string name="group_profile_is_stored_on_members_devices">Group profile is stored on members\' devices, not on the servers.</string>
|
||||
<string name="channel_profile_is_stored_on_subscribers_devices">Channel profile is stored on subscribers\' devices and on the chat relays.</string>
|
||||
<string name="save_group_profile">Save group profile</string>
|
||||
<string name="save_channel_profile">Save channel profile</string>
|
||||
<string name="error_saving_group_profile">Error saving group profile</string>
|
||||
<string name="error_saving_channel_profile">Error saving channel profile</string>
|
||||
|
||||
<!-- NetworkAndServers.kt -->
|
||||
<string name="network_preset_servers_title">Preset servers</string>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@simplex-chat/types",
|
||||
"version": "0.3.0",
|
||||
"version": "0.4.0",
|
||||
"description": "TypeScript types for SimpleX Chat bot libraries",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -813,7 +813,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}}))
|
||||
const r = await this.sendChatCmd(CC.CreateActiveUser.cmdString({newUser: {profile, pastTimestamp: false, userChatRelay: false}}))
|
||||
if (r.type === "activeUser") return r.user
|
||||
throw new ChatCommandError("unexpected response", r)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ cabal-version: 1.12
|
||||
-- see: https://github.com/sol/hpack
|
||||
|
||||
name: simplex-chat
|
||||
version: 6.5.0.12
|
||||
version: 6.5.0.14
|
||||
category: Web, System, Services, Cryptography
|
||||
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
||||
author: simplex.chat
|
||||
|
||||
Reference in New Issue
Block a user