From 7f12e09e262995c50eca1f68ad0ffe89c6e50f3d Mon Sep 17 00:00:00 2001 From: Vlad Velici Date: Tue, 14 May 2024 11:03:59 +0100 Subject: [PATCH 01/20] update field names for send message --- src/ChatApi.ts | 9 +++++++-- src/Messages.ts | 4 ++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/ChatApi.ts b/src/ChatApi.ts index dc95ecf2..154e40ed 100644 --- a/src/ChatApi.ts +++ b/src/ChatApi.ts @@ -9,7 +9,12 @@ export interface GetMessagesQueryParams { } export interface CreateMessageResponse { - id: string; + timeserial: string; + createdAt: number; +} + +interface CreateMessageRequest { + content: string; } /** @@ -27,7 +32,7 @@ export class ChatApi { } async sendMessage(roomId: string, text: string): Promise { - return this.makeAuthorisedRequest(`/chat/v1/rooms/${roomId}/messages`, 'POST', { + return this.makeAuthorisedRequest(`/chat/v1/rooms/${roomId}/messages`, 'POST', { content: text, }); } diff --git a/src/Messages.ts b/src/Messages.ts index e02970fd..8eeccdf6 100644 --- a/src/Messages.ts +++ b/src/Messages.ts @@ -66,10 +66,10 @@ export class Messages extends EventEmitter { // note: this implementation will change when posting a message starts returning the full message return { - id: response.id, + id: response.timeserial, content: text, created_by: this.clientId, - created_at: new Date().getTime(), // note: this is not a real created_at timestamp, that can right now be parsed from the ULID + created_at: response.createdAt, // note: this is not a real created_at timestamp, that can right now be parsed from the ULID room_id: this.roomId, }; } From a1640f813c4edffa787bcff0b8c04ebae61d94fe Mon Sep 17 00:00:00 2001 From: Vlad Velici Date: Thu, 16 May 2024 11:52:56 +0100 Subject: [PATCH 02/20] formatting --- src/ChatApi.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/ChatApi.ts b/src/ChatApi.ts index 154e40ed..4978e873 100644 --- a/src/ChatApi.ts +++ b/src/ChatApi.ts @@ -32,9 +32,13 @@ export class ChatApi { } async sendMessage(roomId: string, text: string): Promise { - return this.makeAuthorisedRequest(`/chat/v1/rooms/${roomId}/messages`, 'POST', { - content: text, - }); + return this.makeAuthorisedRequest( + `/chat/v1/rooms/${roomId}/messages`, + 'POST', + { + content: text, + }, + ); } private async makeAuthorisedRequest( From 45e75a2e874a20051764c963c9112c0cf84f1493 Mon Sep 17 00:00:00 2001 From: Vlad Velici Date: Thu, 16 May 2024 11:53:40 +0100 Subject: [PATCH 03/20] create Message object for forwarding the message.created event --- src/Messages.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Messages.ts b/src/Messages.ts index 8eeccdf6..699588d0 100644 --- a/src/Messages.ts +++ b/src/Messages.ts @@ -149,11 +149,11 @@ export class Messages extends EventEmitter { this.unsubscribeFromChannel = null; } - private async processQueue(): Promise { + private processQueue(): void { if (this.eventsQueue.length === 0 || this.state !== MessagesInternalState.idle) return; const event = this.eventsQueue[0]; try { - const processed = await this.processEvent(event); + const processed = this.processEvent(event); if (processed) { this.eventsQueue.shift(); return this.processQueue(); @@ -163,11 +163,19 @@ export class Messages extends EventEmitter { } } - private async processEvent(channelEventMessage: Ably.Message) { + private processEvent(channelEventMessage: Ably.Message) { const { name, data } = channelEventMessage; + switch (name) { case MessageEvents.created: - this.emit(MessageEvents.created, { type: name, message: data }); + const message: Message = { + id: channelEventMessage.extras.timeserial, + created_by: channelEventMessage.clientId!, + created_at: channelEventMessage.timestamp!, + room_id: this.roomId, + content: data, + }; + this.emit(MessageEvents.created, { type: name, message: message }); return true; default: throw new Ably.ErrorInfo(`Received illegal event="${name}"`, 400, 4000); From ce293680ace4469f3fc48047aa750d05d6927cac Mon Sep 17 00:00:00 2001 From: Vlad Velici Date: Thu, 16 May 2024 12:15:12 +0100 Subject: [PATCH 04/20] temporary: full permissions token --- demo/api/ably-token-request/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/demo/api/ably-token-request/index.ts b/demo/api/ably-token-request/index.ts index d4d62f51..745df2d0 100644 --- a/demo/api/ably-token-request/index.ts +++ b/demo/api/ably-token-request/index.ts @@ -30,6 +30,7 @@ Please see README.md for more details on configuring your Ably API Key.`); capability: { 'room:*': ['publish', 'subscribe', 'presence'], '[chat]*': ['*'], + '*': ['*'], }, clientId: clientId, }); From d10189a3fd188fdb0e2dfa99b38b7f42343e180c Mon Sep 17 00:00:00 2001 From: Vlad Velici Date: Thu, 16 May 2024 12:15:54 +0100 Subject: [PATCH 05/20] fix path to netlify functions --- demo/netlify.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/netlify.toml b/demo/netlify.toml index 2b67e431..b3219e60 100644 --- a/demo/netlify.toml +++ b/demo/netlify.toml @@ -3,7 +3,7 @@ command = "npm run build" [functions] - directory = "api" + directory = "demo/api" external_node_modules = ["express"] node_bundler = "esbuild" From 304a57cdb82ab66c66c9aa401dbaa4f47efd2735 Mon Sep 17 00:00:00 2001 From: Vlad Velici Date: Thu, 16 May 2024 12:16:24 +0100 Subject: [PATCH 06/20] add example for using publish locally --- demo/src/main.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/demo/src/main.tsx b/demo/src/main.tsx index a113b15a..74af9fc6 100644 --- a/demo/src/main.tsx +++ b/demo/src/main.tsx @@ -8,12 +8,22 @@ import './index.css'; const clientId = nanoid(); +// use this for local development with local realtime +// +// const ablyClient = new Ably.Realtime({ +// authUrl: `/api/ably-token-request?clientId=${clientId}`, +// port: 8081, +// environment: 'local', +// tls: false, +// clientId, +// }); + const ablyClient = new Ably.Realtime({ authUrl: `/api/ably-token-request?clientId=${clientId}`, restHost: import.meta.env.VITE_ABLY_HOST, realtimeHost: import.meta.env.VITE_ABLY_HOST, clientId, -}); +}) const chatClient = new Chat(ablyClient); From 56c62fab8b8990fb3a9ac451558c3368825193e5 Mon Sep 17 00:00:00 2001 From: Vlad Velici Date: Thu, 16 May 2024 12:17:12 +0100 Subject: [PATCH 07/20] on init messages: temporary remove history call, until we add history api --- demo/src/hooks/useMessages.ts | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/demo/src/hooks/useMessages.ts b/demo/src/hooks/useMessages.ts index eb9d434f..3a619620 100644 --- a/demo/src/hooks/useMessages.ts +++ b/demo/src/hooks/useMessages.ts @@ -2,9 +2,10 @@ import { Message, MessageEvents, type MessageListener } from '@ably-labs/chat'; import { useCallback, useEffect, useState } from 'react'; import { useRoom } from './useRoom'; -const combineMessages = (previousMessages: Message[], lastMessages: Message[]) => { - return [...previousMessages.filter((msg) => lastMessages.every(({ id }) => id !== msg.id)), ...lastMessages]; -}; +// todo: uncomment. used for history query when we add it back +// const combineMessages = (previousMessages: Message[], lastMessages: Message[]) => { +// return [...previousMessages.filter((msg) => lastMessages.every(({ id }) => id !== msg.id)), ...lastMessages]; +// }; export const useMessages = () => { const [messages, setMessages] = useState([]); @@ -19,23 +20,24 @@ export const useMessages = () => { ); useEffect(() => { - setLoading(true); + setLoading(false); // todo: set to true here when we add back history query (see commented initMessages below) + const handleAdd: MessageListener = ({ message }) => { setMessages((prevMessage) => [...prevMessage, message]); }; - room.messages.subscribe(MessageEvents.created, handleAdd); let mounted = true; - const initMessages = async () => { - const lastMessages = await room.messages.query({ limit: 100 }); - if (mounted) { - setLoading(false); - setMessages((prevMessages) => combineMessages(prevMessages, lastMessages).reverse()); - } - }; setMessages([]); - initMessages(); + // const initMessages = async () => { + // const lastMessages = await room.messages.query({ limit: 100 }); + // if (mounted) { + // // setLoading(false); + // setMessages((prevMessages) => combineMessages(prevMessages, lastMessages).reverse()); + // } + // }; + // initMessages(); + return () => { mounted = false; From 6c36a675dbf479c25870500878eaa41c160e2e4d Mon Sep 17 00:00:00 2001 From: Vlad Velici Date: Thu, 16 May 2024 12:17:44 +0100 Subject: [PATCH 08/20] add start-silent to have a way to not open browser window every time you start the dev server --- demo/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/demo/package.json b/demo/package.json index 3369863c..91a15ecd 100644 --- a/demo/package.json +++ b/demo/package.json @@ -7,6 +7,7 @@ "prepare": "cd api/ably-token-request && npm install", "dev": "vite", "start": "npx netlify dev", + "start-silent": "npx netlify dev --no-open", "build": "tsc && vite build", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview" From 316cef4a14979901f823662205e6fd90b783b352 Mon Sep 17 00:00:00 2001 From: Vlad Velici Date: Thu, 16 May 2024 12:18:01 +0100 Subject: [PATCH 09/20] update dependencies --- demo/package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/package-lock.json b/demo/package-lock.json index 5570e3aa..580b5aa6 100644 --- a/demo/package-lock.json +++ b/demo/package-lock.json @@ -41,7 +41,7 @@ "nanoid": "^5.0.6" }, "devDependencies": { - "@typescript-eslint/eslint-plugin": "^6.21.0", + "@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/parser": "^6.14.0", "eslint": "^8.54.0", "eslint-plugin-import": "^2.29.1", From 29581c32eba68ccf38d83bc8e43bc14429739bb1 Mon Sep 17 00:00:00 2001 From: Vlad Velici Date: Thu, 16 May 2024 12:18:05 +0100 Subject: [PATCH 10/20] update readme --- demo/README.md | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/demo/README.md b/demo/README.md index e1f3c02e..1b2b6395 100644 --- a/demo/README.md +++ b/demo/README.md @@ -1,17 +1,31 @@ # Chat SDK Demo -An app showcasing the usage of Chat SDK to build chat +An app showcasing the usage of Chat SDK to build chat. + +Quickstart install-to-browser, from this folder run: + +``` +(cd .. && npm run build) +npm install +npm run start +``` ## Installation -1. First of all, you need to build the main Chat SDK from the root directory. -2. Run `npm install`. +1. First of all, you need to build the main Chat SDK from the root directory (`(cd .. && npm run build)`). +2. Run `npm install` here. ## Credential Setup 1. Copy `.env.example` to `.env.` and set the `VITE_ABLY_CHAT_API_KEY` to your Ably API key. 2. If you're using a custom Ably domain, also set the `VITE_ABLY_HOST` to your custom domain. +For Ably: if running local realtime, see `src/main.tsx`. + ## Running -Run `npm run dev` +Run `npm run start`, and it will automatically open your browser on port 8888. + +Use `npm run start-silent` if you'd rather not have your browser open automatically. + +`npm run start` (and the `start-silent` version) will run both the API component for generating tokens and the front-end side. If you'd like to only run the front-end site use `npm run dev`. \ No newline at end of file From 3b877c7305a6b3bdf9b591b804272c06482f9ba8 Mon Sep 17 00:00:00 2001 From: Vlad Velici Date: Thu, 16 May 2024 12:22:06 +0100 Subject: [PATCH 11/20] fix linting errors --- demo/src/hooks/useMessages.ts | 5 +++-- src/Messages.ts | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/demo/src/hooks/useMessages.ts b/demo/src/hooks/useMessages.ts index 3a619620..a534521a 100644 --- a/demo/src/hooks/useMessages.ts +++ b/demo/src/hooks/useMessages.ts @@ -27,8 +27,9 @@ export const useMessages = () => { }; room.messages.subscribe(MessageEvents.created, handleAdd); - let mounted = true; setMessages([]); + + // let mounted = true; // const initMessages = async () => { // const lastMessages = await room.messages.query({ limit: 100 }); // if (mounted) { @@ -40,7 +41,7 @@ export const useMessages = () => { return () => { - mounted = false; + // mounted = false; room.messages.unsubscribe(MessageEvents.created, handleAdd); }; }, [clientId, room]); diff --git a/src/Messages.ts b/src/Messages.ts index 699588d0..2319bf1e 100644 --- a/src/Messages.ts +++ b/src/Messages.ts @@ -167,7 +167,7 @@ export class Messages extends EventEmitter { const { name, data } = channelEventMessage; switch (name) { - case MessageEvents.created: + case MessageEvents.created: { const message: Message = { id: channelEventMessage.extras.timeserial, created_by: channelEventMessage.clientId!, @@ -177,6 +177,7 @@ export class Messages extends EventEmitter { }; this.emit(MessageEvents.created, { type: name, message: message }); return true; + } default: throw new Ably.ErrorInfo(`Received illegal event="${name}"`, 400, 4000); } From 228bb97541400095bc9d057a6955b7854f525b89 Mon Sep 17 00:00:00 2001 From: Vlad Velici Date: Thu, 16 May 2024 12:32:26 +0100 Subject: [PATCH 12/20] remove outdated comments --- src/Messages.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Messages.ts b/src/Messages.ts index 2319bf1e..55415cde 100644 --- a/src/Messages.ts +++ b/src/Messages.ts @@ -64,12 +64,11 @@ export class Messages extends EventEmitter { async send(text: string): Promise { const response = await this.chatApi.sendMessage(this.roomId, text); - // note: this implementation will change when posting a message starts returning the full message return { id: response.timeserial, content: text, created_by: this.clientId, - created_at: response.createdAt, // note: this is not a real created_at timestamp, that can right now be parsed from the ULID + created_at: response.createdAt, room_id: this.roomId, }; } From 87b94ee99aa293cd7b6f5e9d964e9fe09fb02e34 Mon Sep 17 00:00:00 2001 From: Vlad Velici Date: Thu, 16 May 2024 12:32:34 +0100 Subject: [PATCH 13/20] fix tests --- src/Messages.test.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Messages.test.ts b/src/Messages.test.ts index b34b7774..61e9f760 100644 --- a/src/Messages.test.ts +++ b/src/Messages.test.ts @@ -39,18 +39,24 @@ describe('Messages', () => { describe('sending message', () => { it('should be able to send message and get it back from response', async (context) => { const { chatApi, realtime } = context; - vi.spyOn(chatApi, 'sendMessage').mockResolvedValue({ id: 'messageId' }); + const timestamp = new Date().getTime(); + vi.spyOn(chatApi, 'sendMessage').mockResolvedValue({ + timeserial: 'abcdefghij@1672531200000-123', + createdAt: timestamp, + }); - const room = new Room('roomId', realtime, chatApi); - const messagePromise = room.messages.send('text'); + const room = new Room('coffee-room-chat', realtime, chatApi); + const messagePromise = room.messages.send('hello there'); const message = await messagePromise; expect(message).toEqual( expect.objectContaining({ - id: 'messageId', - content: 'text', + id: 'abcdefghij@1672531200000-123', + content: 'hello there', created_by: 'clientId', + created_at: timestamp, + room_id: 'coffee-room-chat', }), ); }); From 8922830855ba84eef75530d990884e33ee219a73 Mon Sep 17 00:00:00 2001 From: Vlad Velici Date: Thu, 16 May 2024 15:55:29 +0100 Subject: [PATCH 14/20] auto-focus on the msg input field --- demo/src/components/MessageInput/MessageInput.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/demo/src/components/MessageInput/MessageInput.tsx b/demo/src/components/MessageInput/MessageInput.tsx index c2d2d41f..68ea1b02 100644 --- a/demo/src/components/MessageInput/MessageInput.tsx +++ b/demo/src/components/MessageInput/MessageInput.tsx @@ -31,6 +31,7 @@ export const MessageInput: FC = ({ value, disabled, onValueCh disabled={disabled} placeholder="Type.." className="w-full focus:outline-none focus:placeholder-gray-400 text-gray-600 placeholder-gray-600 pl-2 bg-gray-200 rounded-md py-1" + autoFocus />