From 7bf170cd03c545da7d4f96be6df7b9291625c306 Mon Sep 17 00:00:00 2001 From: Kristina Date: Wed, 25 Dec 2024 17:02:59 +0400 Subject: [PATCH] Init ws client (#7) * Init client-ws --- bun.lockb | Bin 101408 -> 102304 bytes packages/client-ws/package.json | 18 +++ packages/client-ws/src/client.ts | 213 +++++++++++++++++++++++++++ packages/client-ws/src/connection.ts | 123 ++++++++++++++++ packages/client-ws/src/index.ts | 1 + packages/client-ws/tsconfig.json | 9 ++ packages/sdk-types/src/client.ts | 45 ++++++ packages/sdk-types/src/index.ts | 1 + 8 files changed, 410 insertions(+) create mode 100644 packages/client-ws/package.json create mode 100644 packages/client-ws/src/client.ts create mode 100644 packages/client-ws/src/connection.ts create mode 100644 packages/client-ws/src/index.ts create mode 100644 packages/client-ws/tsconfig.json create mode 100644 packages/sdk-types/src/client.ts diff --git a/bun.lockb b/bun.lockb index bcfd9769cc9d102bab1d9e18fd386925410165fd..3bdd61c26d229dbfe9b10bd7edc768e618bbe2ed 100755 GIT binary patch delta 14171 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 delta 13593 zcmeHOcX*V=)_-S{P4*2nBq3#qX#^5N%BHZ{gyc<-n$U|BDK2CQp(G&*WkJXWBBEjeX`hM(5ZhIPg5U4FC4e95z0dRg^*s*$_RN_%bLPyM zGi5j9y0t#*R{AVWihucy0UH;8&AVTmdaTL#?|wYHxioa&>D5Z_F{{p0ZL*)JUAl-# zl+4S9MpREVX^dJT`AL$;iUNO7SLNiA(ke-M!Anxh{ky|)dWYN zya|;TmzGtyB&nayk1r`LN}N_MHAk;h@5S?3aJ3h76Q@9m%v}zix*I>)SykmKk|w#_ z)5-ii2$5V0}(cPOV0o1!=B(8-ypy`vU3HU~GsS(ykE zi&ugtsY#duX!Ps=B{Q58RjZ}DTS-zF_+;)h1y`SgP84|F1tsY{df)3o?*hMEFFy!_ zZmpk(a%%Su>>}+I;K^A5t+hc;tW0z}r%aZd6~*Pwib|I+%4v`}!5U2gg}!Rf5oE|4 zG+|D7Z#)!{;6PAv!od(p3IcV24+XV?l3|87|AAnO1pf#q)laZWQd`h17-IxzfACP@ zSpc5&9Μ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 F{|9?2vC#kk diff --git a/packages/client-ws/package.json b/packages/client-ws/package.json new file mode 100644 index 0000000000..cfe8cb0918 --- /dev/null +++ b/packages/client-ws/package.json @@ -0,0 +1,18 @@ +{ + "name": "@communication/client-ws", + "version": "0.1.0", + "main": "src/index.ts", + "module": "src/index.ts", + "type": "module", + "devDependencies": { + "@types/bun": "^1.1.14" + }, + "dependencies": { + "@communication/types": "workspace:*", + "@communication/sdk-types": "workspace:*", + "@msgpack/msgpack": "^3.0.0-beta2" + }, + "peerDependencies": { + "typescript": "^5.6.3" + } +} diff --git a/packages/client-ws/src/client.ts b/packages/client-ws/src/client.ts new file mode 100644 index 0000000000..b8329f7755 --- /dev/null +++ b/packages/client-ws/src/client.ts @@ -0,0 +1,213 @@ +import { + type CardID, + type ContextID, + type FindMessagesParams, + type FindNotificationContextParams, + type FindNotificationsParams, + type Message, + type MessageID, + type Notification, + type NotificationContext, + type NotificationContextUpdate, + type RichText, + type SocialID +} from '@communication/types' +import { + type BroadcastEvent, + type Client, + type CreateAttachmentEvent, + type CreateMessageEvent, + type CreateMessageResult, + type CreateNotificationContextEvent, + type CreateNotificationContextResult, + type CreateNotificationEvent, + type CreatePatchEvent, + type CreateReactionEvent, + type Event, + type EventResult, + EventType, + type RemoveAttachmentEvent, + type RemoveMessageEvent, + type RemoveNotificationContextEvent, + type RemoveNotificationEvent, + type RemoveReactionEvent, + type UpdateNotificationContextEvent +} from '@communication/sdk-types' + +import { WebSocketConnection } from './connection' + +class WsClient implements Client { + private readonly ws: WebSocketConnection + + onEvent: (event: BroadcastEvent) => void = () => {} + + constructor( + private readonly url: string, + private readonly token: string, + private readonly binary: boolean = false + ) { + const connectionUrl = this.url + '?token=' + this.token + this.ws = new WebSocketConnection(connectionUrl, this.binary) + this.ws.onEvent = (event) => { + void this.onEvent(event) + } + } + + async createMessage(card: CardID, content: RichText, creator: SocialID): Promise { + const event: CreateMessageEvent = { + type: EventType.CreateMessage, + card, + content, + creator + } + const result = await this.sendEvent(event) + return (result as CreateMessageResult).id + } + + async removeMessage(message: MessageID) { + const event: RemoveMessageEvent = { + type: EventType.RemoveMessage, + message + } + await this.sendEvent(event) + } + + async createPatch(message: MessageID, content: RichText, creator: SocialID): Promise { + const event: CreatePatchEvent = { + type: EventType.CreatePatch, + message, + content, + creator + } + await this.sendEvent(event) + } + + async createReaction(message: MessageID, reaction: string, creator: SocialID): Promise { + const event: CreateReactionEvent = { + type: EventType.CreateReaction, + message, + reaction, + creator + } + await this.sendEvent(event) + } + + async removeReaction(message: MessageID, reaction: string, creator: SocialID): Promise { + const event: RemoveReactionEvent = { + type: EventType.RemoveReaction, + message, + reaction, + creator + } + await this.sendEvent(event) + } + + async createAttachment(message: MessageID, card: CardID, creator: SocialID): Promise { + const event: CreateAttachmentEvent = { + type: EventType.CreateAttachment, + message, + card, + creator + } + await this.sendEvent(event) + } + + async removeAttachment(message: MessageID, card: CardID): Promise { + const event: RemoveAttachmentEvent = { + type: EventType.RemoveAttachment, + message, + card + } + await this.sendEvent(event) + } + + async findMessages(params: FindMessagesParams, queryId?: number): Promise { + const rawMessages = await this.ws.send('findMessages', [params, queryId]) + return rawMessages.map(this.toMessage) + } + + toMessage(raw: any): Message { + return { + id: raw.id, + content: raw.content, + creator: raw.creator, + created: new Date(raw.created), + edited: new Date(raw.edited), + reactions: raw.reactions, + attachments: raw.attachments + } + } + + async createNotification(message: MessageID, context: ContextID): Promise { + const event: CreateNotificationEvent = { + type: EventType.CreateNotification, + message, + context + } + await this.sendEvent(event) + } + + async removeNotification(message: MessageID, context: ContextID): Promise { + const event: RemoveNotificationEvent = { + type: EventType.RemoveNotification, + message, + context + } + await this.sendEvent(event) + } + + async createNotificationContext(card: CardID, lastView?: Date, lastUpdate?: Date): Promise { + const event: CreateNotificationContextEvent = { + type: EventType.CreateNotificationContext, + card, + lastView, + lastUpdate + } + const result = await this.sendEvent(event) + return (result as CreateNotificationContextResult).id + } + + async removeNotificationContext(context: ContextID): Promise { + const event: RemoveNotificationContextEvent = { + type: EventType.RemoveNotificationContext, + context + } + await this.sendEvent(event) + } + + async updateNotificationContext(context: ContextID, update: NotificationContextUpdate): Promise { + const event: UpdateNotificationContextEvent = { + type: EventType.UpdateNotificationContext, + context, + update + } + await this.sendEvent(event) + } + + async findNotificationContexts( + params: FindNotificationContextParams, + queryId?: number + ): Promise { + return await this.ws.send('findNotificationContexts', [params, queryId]) + } + + async findNotifications(params: FindNotificationsParams, queryId?: number): Promise { + return await this.ws.send('findNotifications', [params, queryId]) + } + + async unsubscribeQuery(id: number): Promise { + await this.ws.send('unsubscribeQuery', [id]) + } + + private async sendEvent(event: Event): Promise { + return await this.ws.send('event', [event]) + } + + close() { + void this.ws.close() + } +} + +export async function getWebsocketClient(url: string, token: string): Promise { + return new WsClient(url, token) +} diff --git a/packages/client-ws/src/connection.ts b/packages/client-ws/src/connection.ts new file mode 100644 index 0000000000..679d3f8d06 --- /dev/null +++ b/packages/client-ws/src/connection.ts @@ -0,0 +1,123 @@ +import type { Response, HelloRequest, RequestId, BroadcastEvent, Request } from '@communication/sdk-types' +import { encode, decode } from '@msgpack/msgpack' + +const PING_TIMEOUT = 10000 +const RECONNECT_TIMEOUT = 1000 + +export class WebSocketConnection { + private ws!: WebSocket | Promise + private requests: { [key: RequestId]: { resolve: (response: any) => void; reject: (reason: any) => void } } = {} + private lastId: number = 0 + + private pingInterval: any + private reconnectTimeout: any + + onEvent: (event: BroadcastEvent) => void = () => {} + + constructor( + private url: string, + private readonly binary: boolean = false + ) { + this.connect() + } + + private connect(): void { + const ws = new WebSocket(this.url) + + ws.onmessage = (event: MessageEvent) => { + const response = deserializeResponse(event.data, this.binary) + if (response.id !== undefined) { + const handlers = this.requests[response.id] + if (handlers === undefined) return + delete this.requests[response.id] + if (response.error !== undefined) { + console.error('Websocket error', response.error) + handlers.reject(response.error) + } else { + handlers.resolve(response.result) + } + } else { + if (response.error !== undefined) { + console.error('Websocket error', response.error) + } else { + const event = response.result as BroadcastEvent + this.onEvent(event) + } + } + } + + ws.onclose = () => { + clearInterval(this.pingInterval) + this.handleReconnect() + } + + this.ws = new Promise((resolve, reject) => { + ws.onopen = () => { + const request: HelloRequest = { id: 'hello', method: 'hello', params: [], binary: this.binary } + ws.send(serializeRequest(request, this.binary)) + clearInterval(this.pingInterval) + this.pingInterval = setInterval(() => { + void this.sendRequest({ method: 'ping', params: [] }) + }, PING_TIMEOUT) + resolve(ws) + } + ws.onerror = (event: any) => { + console.error('Websocket error', event) + reject(new Error('Websocket error')) + } + }) + } + + private handleReconnect() { + clearTimeout(this.reconnectTimeout) + this.reconnectTimeout = setTimeout(() => { + this.connect() + }, RECONNECT_TIMEOUT) + } + + async waitWs(): Promise { + if (this.ws instanceof Promise) { + this.ws = await this.ws + } + return this.ws + } + + async send(method: string, params: any[]): Promise { + const id = ++this.lastId + return await this.sendRequest({ id: id.toString(), method, params }) + } + + private async sendRequest(request: Request): Promise { + const ws = await this.waitWs() + + return new Promise((resolve, reject) => { + if (request.id !== undefined) { + this.requests[request.id] = { resolve, reject } + } + ws.send(serializeRequest(request, this.binary)) + }) + } + + async close(): Promise { + clearInterval(this.pingInterval) + clearTimeout(this.reconnectTimeout) + const ws = await this.waitWs() + ws.close() + } +} + +function serializeRequest(request: Request, binary: boolean): any { + if (binary) { + return encode(request) + } else { + return JSON.stringify(request) + } +} + +function deserializeResponse(data: any, binary: boolean): Response { + if (binary) { + return decode(data) as Response + } else { + return JSON.parse(data.toString()) + } +} diff --git a/packages/client-ws/src/index.ts b/packages/client-ws/src/index.ts new file mode 100644 index 0000000000..83dae7638c --- /dev/null +++ b/packages/client-ws/src/index.ts @@ -0,0 +1 @@ +export * from './client' diff --git a/packages/client-ws/tsconfig.json b/packages/client-ws/tsconfig.json new file mode 100644 index 0000000000..3ae07cd3fa --- /dev/null +++ b/packages/client-ws/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "jsx": "react-jsx", + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src"] +} diff --git a/packages/sdk-types/src/client.ts b/packages/sdk-types/src/client.ts new file mode 100644 index 0000000000..dce6d468e9 --- /dev/null +++ b/packages/sdk-types/src/client.ts @@ -0,0 +1,45 @@ +import type { + CardID, + ContextID, + FindNotificationContextParams, + FindNotificationsParams, + Message, + MessageID, + NotificationContext, + NotificationContextUpdate, + RichText, + SocialID, + Notification +} from '@communication/types' +import type { FindMessagesParams } from '@communication/types' + +import type { BroadcastEvent } from './event.ts' + +export interface Client { + createMessage(card: CardID, content: RichText, creator: SocialID): Promise + removeMessage(id: MessageID): Promise + createPatch(message: MessageID, content: RichText, creator: SocialID): Promise + + createReaction(message: MessageID, reaction: string, creator: SocialID): Promise + removeReaction(message: MessageID, reaction: string, creator: SocialID): Promise + + createAttachment(message: MessageID, card: CardID, creator: SocialID): Promise + removeAttachment(message: MessageID, card: CardID): Promise + + createNotification(message: MessageID, context: ContextID): Promise + removeNotification(message: MessageID, context: ContextID): Promise + + createNotificationContext(card: CardID, lastView?: Date, lastUpdate?: Date): Promise + removeNotificationContext(context: ContextID): Promise + updateNotificationContext(context: ContextID, update: NotificationContextUpdate): Promise + + onEvent(event: BroadcastEvent): void + + findMessages(params: FindMessagesParams, queryId?: number): Promise + findNotificationContexts(params: FindNotificationContextParams, queryId?: number): Promise + findNotifications(params: FindNotificationsParams, queryId?: number): Promise + + unsubscribeQuery(id: number): Promise + close(): void +} + diff --git a/packages/sdk-types/src/index.ts b/packages/sdk-types/src/index.ts index 084f5c7e90..38c4587b9e 100644 --- a/packages/sdk-types/src/index.ts +++ b/packages/sdk-types/src/index.ts @@ -1,3 +1,4 @@ export * from './db' export * from './event' export * from './ws' +export * from './client' \ No newline at end of file