Skip to content

Commit

Permalink
Conversation.sendMessage API, e2e tests and fabric-http demo update (#…
Browse files Browse the repository at this point in the history
…999)

* Conversation.sendMessage API, unit tests and fabric-http deomo update

* rename conversation_id to addressId in public api

* refactored so that conversation obj returned from getConversations() has sendMessage() method updated e2e and tests

* added getMessage method to conversation objects returned from getConversations and updated e2e tests

* added changesets

* removed unused log and reverted package-lock.json changes

* some refactoring

* some refactoring

---------

Co-authored-by: Aye Min Aung <ayemin@signalwire.com>
  • Loading branch information
ayeminag and Aye Min Aung authored Apr 3, 2024
1 parent e1f49fd commit 6d71362
Show file tree
Hide file tree
Showing 12 changed files with 307 additions and 41 deletions.
9 changes: 9 additions & 0 deletions .changeset/unlucky-icons-burn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@signalwire/core': minor
'@signalwire/js': minor
---

- `client.conversations.sendMessage()`
- `conversation.sendMessage()` API for conversation object returned from `getConversations()` API
- `conversation.getMessages()` API for conversation object returned from `getConversations()`
- added e2e tests for conversation (room)
116 changes: 116 additions & 0 deletions internal/e2e-js/tests/callfabric/conversationRoom.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { test, expect } from '../../fixtures'
import {
SERVER_URL,
createTestSATToken,
createVideoRoom,
createCFClient
} from '../../utils'
import { uuid } from '@signalwire/core'

test.describe('Conversation Room', () => {
test('send message in a room conversation', async ({ createCustomVanillaPage }) => {
const page = await createCustomVanillaPage({ name: '[page]' })
const page2 = await createCustomVanillaPage({
name: '[page2]'
})
await page.goto(SERVER_URL)
await page2.goto(SERVER_URL)

const sat1 = await createTestSATToken()
await createCFClient(page, sat1)
const sat2 = await createTestSATToken()
await createCFClient(page2, sat2)

const roomName = `e2e-js-convo-room_${uuid()}`
await createVideoRoom(roomName)

const firstMsgEvent = await page.evaluate(({ roomName }) => {
return new Promise(async (resolve) => {
// @ts-expect-error
const client = window._client
const addresses = await client.addresses.getAddresses({ displayName: roomName })
const roomAddress = addresses.data[0]
const addressId = roomAddress.id
client.conversation.subscribe(resolve)
client.conversation.sendMessage({
text: '1st message from 1st subscriber',
addressId,
})
})
}, { roomName })

// @ts-expect-error
expect(firstMsgEvent.type).toBe('message')

// @ts-expect-error
const addressId = firstMsgEvent.address_id

const secondMsgEventPromiseFromPage1 = page.evaluate(() => {
return new Promise(resolve => {
// @ts-expect-error
const client = window._client
client.conversation.subscribe(resolve)
})
})

const firstMsgEventFromPage2 = await page2.evaluate(({ addressId }) => {
return new Promise(async (resolve) => {
// @ts-expect-error
const client = window._client
await client.connect()
client.conversation.subscribe(resolve)
const result = await client.conversation.getConversations()
const convo = result.data.filter(c => c.id == addressId)[0]
convo.sendMessage({
text: '1st message from 2nd subscriber',
})
})
}, { addressId })

const secondMsgEventFromPage1 = await secondMsgEventPromiseFromPage1
expect(secondMsgEventFromPage1).not.toBeUndefined()

// @ts-expect-error
expect(secondMsgEventFromPage1.type).toBe('message')

expect(firstMsgEventFromPage2).not.toBeUndefined()

// @ts-expect-error
expect(firstMsgEventFromPage2.type).toBe('message')

const messages = await page.evaluate(async ({ addressId }) => {
// @ts-expect-error
const client = window._client
const result = await client.conversation.getConversations()
const convo = result.data.filter(c => c.id == addressId)[0]
return await convo.getMessages({})
}, { addressId })

expect(messages).not.toBeUndefined()

expect(messages.data.length).toEqual(2)
expect(messages.data[0]).toMatchObject({
"conversation_id": addressId,
"details": {},
"id": expect.anything(),
"kind": null,
"subtype": "chat",
"text": "1st message from 2nd subscriber",
"ts": expect.anything(),
"type": "message",
"user_id": expect.anything()
})

expect(messages.data[1]).toMatchObject({
"conversation_id": addressId,
"details": {},
"id": expect.anything(),
"kind": null,
"subtype": "chat",
"text": "1st message from 1st subscriber",
"ts": expect.anything(),
"type": "message",
"user_id": expect.anything()
})
})
})
25 changes: 22 additions & 3 deletions internal/e2e-js/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,8 @@ export const createTestRoomSessionWithJWT = async (
)
}

export const createCFClient = async (page: Page) => {
const sat = await createTestSATToken()
export const createCFClient = async (page: Page, sat?: string) => {
if (!sat) sat = await createTestSATToken()
if (!sat) {
console.error('Invalid SAT. Exiting..')
process.exit(4)
Expand All @@ -200,7 +200,6 @@ export const createCFClient = async (page: Page) => {

// @ts-expect-error
window._client = client

return client
},
{
Expand Down Expand Up @@ -280,10 +279,30 @@ export const createTestSATToken = async () => {
}),
}
)
console.log(response.body.read().toString())
const data = await response.json()
return data.token
}


export const createVideoRoom= async (name?: string) => {
const response = await fetch(
`https://${process.env.API_HOST}/api/fabric/resources/video_rooms`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${BASIC_TOKEN}`,
},
body: JSON.stringify({
name: name ? name : `e2e-js-test-room_${uuid()}`,
}),
}
)
const data = await response.json()
return data
}

interface CreateTestCRTOptions {
ttl: number
member_id: string
Expand Down
4 changes: 4 additions & 0 deletions internal/playground-js/src/fabric-http/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,10 @@ <h2 class="title mt-2"></h2>
<span class="placeholder placeholder-lg col-12"></span>
</li>
</ul>
<div class="input-group mt-4 container-fluid">
<input type="text" id="new-conversation-message" class="form-control" placeholder="Message..." aria-label="Chat Message" aria-describedby="button-addon2">
<button class="btn btn-primary" type="button" id="send-message"><i class="bi-send"></i></button>
</div>
</div>
</div>
</div>
Expand Down
31 changes: 27 additions & 4 deletions internal/playground-js/src/fabric-http/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { SignalWire } from '@signalwire/js'

const searchInput = document.getElementById('searchInput')
const searchType = document.getElementById('searchType')
const conversationMessageInput = document.getElementById('new-conversation-message')
const sendMessageBtn = document.getElementById('send-message')

let client = null

Expand Down Expand Up @@ -156,9 +158,16 @@ const createAddressListItem = (address) => {
const button = document.createElement('button')
button.className = 'btn btn-sm btn-success'

button.addEventListener('click', () => dialAddress(channelValue))

const icon = document.createElement('i')
if (channelName != 'messaging') {
button.addEventListener('click', () => dialAddress(channelValue))
} else {

button.addEventListener('click', () => {
subscribeToNewMessages()
openMessageModal(address)
})
}
if (channelName === 'messaging') {
icon.className = 'bi bi-chat'
} else if (channelName === 'video') {
Expand Down Expand Up @@ -189,6 +198,7 @@ function updateAddressUI() {
addresses
.map(createAddressListItem)
.forEach((item) => addressUl.appendChild(item))
subscribeToNewMessages();
}

async function fetchAddresses() {
Expand All @@ -212,7 +222,6 @@ async function fetchAddresses() {
window.dialAddress = async (address) => {
const destinationInput = document.getElementById('destination')
destinationInput.value = address
connect()
}

window.fetchNextAddresses = async () => {
Expand Down Expand Up @@ -246,6 +255,17 @@ searchInput.addEventListener('input', () => {

searchType.addEventListener('change', fetchAddresses)

sendMessageBtn.addEventListener('click', async () => {
if (!client) return
const address = window.__currentAddress
const text = conversationMessageInput.value
await client.conversation.sendMessage({
addressId: address.id,
text,
})
conversationMessageInput.value = ''
})

/** ======= Address utilities end ======= */

/** ======= History utilities start ======= */
Expand Down Expand Up @@ -387,8 +407,9 @@ function createMessageListItem(msg) {
listItem.innerHTML = `
<div class="d-flex flex-column">
<div class="d-flex justify-content-between align-items-center">
<h6 class="mb-0 text-capitalize">${msg.type ?? 'unknown'}</h6>
<h6 class="mb-0 text-capitalize">${msg.text}</h6>
<div class="d-flex align-items-center gap-1">
<span class="badge bg-info">${msg.type}</span>
<span class="badge bg-info">${msg.subtype ?? 'unknown'}</span>
<span class="badge bg-success">${msg.kind ?? 'unknown'}</span>
</div>
Expand Down Expand Up @@ -426,9 +447,11 @@ function clearMessageModal() {
if (avatarImage) {
avatarImage.src = newImageUrl
}
window.__currentAddress = undefined
}

async function openMessageModal(data) {
window.__currentAddress = data
const modal = new bootstrap.Modal(msgModalDiv)
modal.show()

Expand Down
26 changes: 26 additions & 0 deletions packages/core/src/types/callfabric.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@ export interface PaginatedResponse<T> {
}
}

export interface PaginatedResult<T> {
data: Array<T> | []
self() : Promise<PaginatedResult<T> | undefined>
nextPage() : Promise<PaginatedResult<T> | undefined>
prevPage() : Promise<PaginatedResult<T> | undefined>
firstPage() : Promise<PaginatedResult<T> | undefined>
hasNext : boolean
hasPrev : boolean
}


/**
* Addresses
*/
Expand Down Expand Up @@ -36,6 +47,12 @@ export interface FetchAddressResponse extends PaginatedResponse<Address> {}
/**
* Conversations
*/
export interface SendConversationMessageOptions {
text: string
addressId: string
metadata?: Record<string, any>
details?: Record<string, any>
}
export interface GetConversationsOptions {
pageSize?: number
}
Expand All @@ -46,6 +63,15 @@ export interface Conversation {
last_message_at: number
metadata: Record<string, any>
name: string
sendMessage(options: { text: string }): Promise<SendConversationMessageResponse>
getMessages(options: { pageSize?: number }): Promise<PaginatedResult<ConversationMessage>>
}

export interface SendConversationMessageResponse {
table: {
conversation_id: string
text: string
}
}

export interface FetchConversationsResponse
Expand Down
Loading

0 comments on commit 6d71362

Please sign in to comment.