Skip to content

Commit

Permalink
cleaner WebSocket hook api, useJsonMessage
Browse files Browse the repository at this point in the history
  • Loading branch information
yoiang committed Mar 1, 2022
1 parent 4d853e0 commit 405bdce
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 48 deletions.
5 changes: 5 additions & 0 deletions src/components/WebSocket/ConnectionClosedError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export class ConnectionClosedError extends Error {
constructor() {
super('Connection closed error')
}
}
21 changes: 21 additions & 0 deletions src/components/WebSocket/JsonMessageParsingError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export class JsonMessageParsingError extends Error {
constructor(
private _connection: WebSocket | undefined,
private _error: unknown,
private _text: string
) {
super('Json message parsing error')
}

get connection(): WebSocket | undefined {
return this._connection
}

get error(): unknown {
return this._error
}

get text(): string {
return this._text
}
}
2 changes: 1 addition & 1 deletion src/components/WebSocket/WebSocketError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export class WebSocketError extends Error {
private _connection: WebSocket | undefined,
private _errorEvent: Event
) {
super('Invalid identify message')
super('WebSocket error')
}

get connection(): WebSocket | undefined {
Expand Down
41 changes: 3 additions & 38 deletions src/components/WebSocket/index.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,5 @@
export * from './useClientConnection'
export * from './WebSocketError'

// export class Connection extends React.Component<ConnectionProps> {
// componentDidMount() {

// this.connection.onmessage = (packet: MessageEvent<any>) => {
// if (!onMessage) {
// return
// }
// try {
// // TODO: fix
// const workaround = packet as unknown
// const message = processPacketIntoMessage(workaround as Packet)
// onMessage(message)
// } catch (error) {
// if (error instanceof InvalidMessageException) {
// console.log(
// 'Error: invalid message:',
// error.type,
// JSON.stringify(error.packet)
// )
// } else {
// console.log('Error: parsing message:', error)
// }
// }
// }
// }
export * from './useJsonMessageClientConnection'

// sendMessage(message: Message) {
// if (!this.connection) {
// throw new Error('Connection is not open')
// }
// sendMessage(this.connection, message)
// }

// render() {
// return null
// }
// }
export * from './ConnectionClosedError'
export * from './WebSocketError'
4 changes: 2 additions & 2 deletions src/components/WebSocket/useClientConnection.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const Default = ({ sendStringToServer, sendStringToClient }: Props) => {
console.error(e)
}

const clientConnection = useClientConnection({
const { send: clientSend } = useClientConnection({
url,
onMessageDataText: (_, data: string) => {
setClientReceivedMessage(`string: '${data}'`)
Expand All @@ -59,7 +59,7 @@ export const Default = ({ sendStringToServer, sendStringToClient }: Props) => {
<br />
<button
onClick={() => {
clientConnection.current.send(sendStringToServer)
clientSend(sendStringToServer)
}}
>
Send String to Server
Expand Down
43 changes: 36 additions & 7 deletions src/components/WebSocket/useClientConnection.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { RefObject, useCallback, useEffect, useRef, useState } from 'react'
import { useCallback, useEffect, useRef } from 'react'
import { ConnectionClosedError } from './ConnectionClosedError'
import { WebSocketError } from './WebSocketError'

interface Props<T = any> {
// TODO: support protocols and extensions

export type DataType = string | ArrayBufferLike | Blob | ArrayBufferView
interface Props<T = DataType> {
url: string

onOpen?: (connection: WebSocket | undefined, event: Event) => void
Expand All @@ -11,7 +15,7 @@ interface Props<T = any> {

onMessage?: (
connection: WebSocket | undefined,
message: MessageEvent<any>
message: MessageEvent<T>
) => void

onMessageData?: (connection: WebSocket | undefined, data: T) => void
Expand All @@ -28,9 +32,13 @@ interface Props<T = any> {
unmountCloseMessage?: string
}

export type Result = RefObject<WebSocket | undefined>
interface Result {
send: (data: DataType) => void
readyState: () => number
close: (code?: number, reason?: string) => void
}

export const useClientConnection = <T = any>({
export const useClientConnection = <T = DataType>({
url,

binaryType = 'arraybuffer',
Expand Down Expand Up @@ -132,7 +140,7 @@ export const useClientConnection = <T = any>({
)

const onmessage = useCallback(
(event: MessageEvent<any>) => {
(event: MessageEvent<T>) => {
onmessageBasic(event)
onmessageData(event)
onmessageDataText(event)
Expand All @@ -155,6 +163,27 @@ export const useClientConnection = <T = any>({
[onError]
)

const send = useCallback((data: DataType) => {
if (!connection.current) {
throw new ConnectionClosedError()
}
connection.current.send(data)
}, [])

const readyState = useCallback(() => {
if (!connection.current) {
return WebSocket.CLOSED
}
return connection.current.readyState
}, [])

const close = useCallback((code?: number, reason?: string) => {
if (!connection.current) {
throw new ConnectionClosedError()
}
connection.current.close(code, reason)
}, [])

useEffect(() => {
const result = new WebSocket(url)

Expand Down Expand Up @@ -183,5 +212,5 @@ export const useClientConnection = <T = any>({
unmountCloseMessage
])

return connection
return { send, readyState, close }
}
88 changes: 88 additions & 0 deletions src/components/WebSocket/useJsonMessageClientConnection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { useCallback } from 'react'
import { JsonMessageParsingError } from './JsonMessageParsingError'
import { useClientConnection, DataType } from './useClientConnection'

interface Props<T> {
url: string

onOpen?: (connection: WebSocket | undefined, event: Event) => void
onClose?: (connection: WebSocket | undefined, event: CloseEvent) => void

binaryType?: BinaryType

onMessage?: (connection: WebSocket | undefined, data: T) => void

onError?: (connection: WebSocket | undefined, error: Error) => void

unmountCloseMessage?: string
}

interface Result<T> {
send: (data: T) => void
readyState: () => number
close: (code?: number, reason?: string) => void

internal: {
send: (data: DataType) => void
}
}

export const useJsonMessageClientConnection = <T>({
url,
onOpen,
onClose,

onMessage,

onError,

unmountCloseMessage = 'Browsing away'
}: Props<T>): Result<T> => {
// TODO: support binary json sending

const onMessageDataText = useCallback(
(connection: WebSocket | undefined, text: string) => {
if (!onMessage) {
return
}
try {
const message = JSON.parse(text)
onMessage(connection, message) // TODO: separate and handle with a different try/catch and error report
} catch (error) {
if (onError) {
onError(
connection,
new JsonMessageParsingError(connection, error, text)
)
}
}
},
[onMessage, onError]
)

const {
send: connectionSend,
close,
readyState
} = useClientConnection<T>({
url,
onOpen,
onClose,
onMessageDataText,
onError,
unmountCloseMessage
})

const send = useCallback((data: T) => {
connectionSend(JSON.stringify(data))
}, [])

return {
send,
readyState,
close,
internal: {
send: connectionSend
}
}
}

0 comments on commit 405bdce

Please sign in to comment.