Skip to content

Commit

Permalink
feat: add server utils
Browse files Browse the repository at this point in the history
  • Loading branch information
alexzhang1030 committed May 12, 2024
1 parent bf70072 commit e9707fa
Show file tree
Hide file tree
Showing 8 changed files with 70 additions and 16 deletions.
28 changes: 22 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,30 @@ wshe.subscribeRaw((data) => {})
const unsubscribe = wshe.subscribe('<eventName>', (payload) => {})
```

Noticed that wshe will send `ping` message every 5 seconds for keep the connection alive. You need response `pong`.
## Server implementation

You should implement a server that can handle the following message format:

```json
{
"event": "<eventName>",
"payload": "<eventPayload>"
}
```

And do this on `onmessage` to handle heartbeat requests:

```ts
// _$WSHE is required for auto JSON.parse
ws.send(`_$WHSE${JSON.stringify({
event: 'pong',
createAt: Date.now(),
})}`)
import { getHeartbeatResponse, isHeartbeatRequest } from 'wshe/server'

ws.onmessage = (message) => {
if (isHeartbeatRequest(message)) {
ws.send(getHeartbeatResponse(message))
return
}
// do your own logic here..., e.g.
JSON.parse(message)
}
```

## TypeScript Support
Expand Down
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"./server": {
"import": "./dist/server.js",
"require": "./dist/server.cjs"
}
},
"main": "./dist/index.cjs",
Expand Down
15 changes: 13 additions & 2 deletions src/__test__/heartbeat.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { createWSHE } from '..'
import { withSign } from '../utils'
import { createMockWSServer } from './utils'
import { getHeartbeatResponse, isHeartbeatRequest } from '@/heartbeat'

describe('heartbeat', () => {
const date = new Date(2000, 1, 1)
Expand Down Expand Up @@ -51,8 +52,8 @@ describe('heartbeat', () => {
expect(message).toBe(message)
expect(pingSpy).toHaveBeenCalledTimes(1)

mockWSServer.ws?.send(getHeartbeatResponse())
message = undefined
mockWSServer.ws?.send(withSign(JSON.stringify({ event: 'pong' })))
await vi.waitFor(() => {
if (message === undefined)
throw new Error('No message received')
Expand All @@ -61,6 +62,17 @@ describe('heartbeat', () => {
expect(pingSpy).toHaveBeenCalledTimes(2)
})

it('test isHeartbeatRequest', () => {
const ping = 'ping'
const pong = 'pong'

const pingMessage = withSign(JSON.stringify({ event: ping }))
const pongMessage = withSign(JSON.stringify({ event: pong }))

expect(isHeartbeatRequest(pingMessage, ping)).toBe(true)
expect(isHeartbeatRequest(pongMessage, ping)).toBe(false)
})

it('must close the connection if no response is received from the server', async () => {
const interval = 100
const timeout = 200
Expand All @@ -84,7 +96,6 @@ describe('heartbeat', () => {
},
)

// Assert
expect(wshe.ws?.readyState).toBe(window.WebSocket.CLOSED)
})
})
24 changes: 24 additions & 0 deletions src/heartbeat.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import destr from 'destr'
import { close } from './close'
import { send } from './send'
import type { ResolvedWSHEConfig, WSHEMessage } from './types'
import { isWithSign, omitSign, withSign } from './utils'

let heartbeatTimeoutWait: ReturnType<typeof setTimeout> | undefined

Expand All @@ -26,3 +28,25 @@ export function heartbeatStop(): void {
clearTimeout(heartbeatTimeoutWait)
heartbeatTimeoutWait = undefined
}

/* c8 ignore start */
export function isHeartbeatRequest(rawData: any, pingMessage = 'ping') {
const str = typeof rawData === 'string' ? rawData : rawData.toString()
if (isWithSign(str)) {
try {
const data = destr<any>(omitSign(str))
return data.event === pingMessage
}
catch {
return false
}
}
return false
}

export function getHeartbeatResponse(pongMessage = 'pong') {
return withSign(JSON.stringify({
event: pongMessage,
}))
}
/* c8 ignore stop */
6 changes: 6 additions & 0 deletions src/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/* c8 ignore start */
export { withSign, isWithSign, omitSign } from './utils'
export { SIGN } from './constants'

export { isHeartbeatRequest, getHeartbeatResponse } from './heartbeat'
/* c8 ignore stop */
5 changes: 0 additions & 5 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,6 @@ export interface WSHEConfig {
* @default
*/
heartbeat?: WSHEHeartbeatConfig
/**
* Retry times when disconnected
* @default 5
*/
retryTimes?: number
}

export type ResolvedWSHEConfig = Required<WSHEConfig> & {
Expand Down
2 changes: 0 additions & 2 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export function resolveRawConfig(config: WSHEConfig): ResolvedWSHEConfig {
const {
debugging = false,
immediate = false,
retryTimes = 5,

onError = noop,
onConnected = noop,
Expand All @@ -26,7 +25,6 @@ export function resolveRawConfig(config: WSHEConfig): ResolvedWSHEConfig {
return {
debugging,
immediate,
retryTimes,
onError,
onConnected,
onDisconnected,
Expand Down
2 changes: 1 addition & 1 deletion tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { defineConfig } from 'tsup'

export default defineConfig({
clean: true,
entry: ['src/index.ts'],
entry: ['src/index.ts', 'src/server.ts'],
target: 'esnext',
format: ['esm', 'cjs'],
dts: true,
Expand Down

0 comments on commit e9707fa

Please sign in to comment.