1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
|
// Copyright (c) 2026 Yuren Hao
// Licensed under AGPL-3.0 - see LICENSE file
// Socket.IO v0.9 protocol encoding/decoding
export interface ParsedMessage {
type: 'disconnect' | 'connect' | 'heartbeat' | 'event' | 'ack' | 'error' | 'noop'
id?: number
data?: unknown
name?: string
args?: unknown[]
}
/**
* Parse a Socket.IO v0.9 message frame.
*
* Frame format:
* 0:: disconnect
* 1:: connect
* 2:: heartbeat
* 5:::{"name":"x","args":[...]} event
* 5:N+::{"name":"x","args":[...]} event with ack request
* 6:::N+[jsonData] ack response
* 8:: noop
*/
export function parseSocketMessage(raw: string): ParsedMessage | null {
if (!raw || raw.length === 0) return null
const type = raw[0]
switch (type) {
case '0':
return { type: 'disconnect' }
case '1':
return { type: 'connect' }
case '2':
return { type: 'heartbeat' }
case '8':
return { type: 'noop' }
case '5': {
// Event: 5:::{"name":"x","args":[...]} or 5:N+::{"name":"x","args":[...]}
const ackMatch = raw.match(/^5:(\d+)\+::(.*)$/s)
if (ackMatch) {
try {
const payload = JSON.parse(ackMatch[2])
return {
type: 'event',
id: parseInt(ackMatch[1]),
name: payload.name,
args: payload.args || []
}
} catch {
return null
}
}
const evtMatch = raw.match(/^5:::(.*)$/s)
if (evtMatch) {
try {
const payload = JSON.parse(evtMatch[1])
return { type: 'event', name: payload.name, args: payload.args || [] }
} catch {
return null
}
}
return null
}
case '6': {
// Ack: 6:::N+[jsonData]
const ackMatch = raw.match(/^6:::(\d+)\+([\s\S]*)/)
if (ackMatch) {
try {
const data = JSON.parse(ackMatch[2])
return { type: 'ack', id: parseInt(ackMatch[1]), data }
} catch {
return { type: 'ack', id: parseInt(ackMatch[1]), data: null }
}
}
return null
}
default:
return null
}
}
/** Encode a Socket.IO v0.9 event (no ack) */
export function encodeEvent(name: string, args: unknown[]): string {
return '5:::' + JSON.stringify({ name, args })
}
/** Encode a Socket.IO v0.9 event that expects an ack response */
export function encodeEventWithAck(ackId: number, name: string, args: unknown[]): string {
return `5:${ackId}+::` + JSON.stringify({ name, args })
}
/** Encode a heartbeat response */
export function encodeHeartbeat(): string {
return '2::'
}
|