-
Notifications
You must be signed in to change notification settings - Fork 554
/
frames.ts
95 lines (91 loc) · 2.47 KB
/
frames.ts
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
import * as uint8arrays from 'uint8arrays'
import { cborEncode, cborDecodeMulti } from '@atproto/common'
import {
frameHeader,
FrameHeader,
FrameType,
MessageFrameHeader,
ErrorFrameHeader,
ErrorFrameBody,
errorFrameBody,
} from './types'
export abstract class Frame {
abstract header: FrameHeader
body: unknown
get op(): FrameType {
return this.header.op
}
toBytes(): Uint8Array {
return uint8arrays.concat([cborEncode(this.header), cborEncode(this.body)])
}
isMessage(): this is MessageFrame<unknown> {
return this.op === FrameType.Message
}
isError(): this is ErrorFrame {
return this.op === FrameType.Error
}
static fromBytes(bytes: Uint8Array) {
const decoded = cborDecodeMulti(bytes)
if (decoded.length > 2) {
throw new Error('Too many CBOR data items in frame')
}
const header = decoded[0]
let body: unknown = kUnset
if (decoded.length > 1) {
body = decoded[1]
}
const parsedHeader = frameHeader.safeParse(header)
if (!parsedHeader.success) {
throw new Error(`Invalid frame header: ${parsedHeader.error.message}`)
}
if (body === kUnset) {
throw new Error('Missing frame body')
}
const frameOp = parsedHeader.data.op
if (frameOp === FrameType.Message) {
return new MessageFrame(body, {
type: parsedHeader.data.t,
})
} else if (frameOp === FrameType.Error) {
const parsedBody = errorFrameBody.safeParse(body)
if (!parsedBody.success) {
throw new Error(`Invalid error frame body: ${parsedBody.error.message}`)
}
return new ErrorFrame(parsedBody.data)
} else {
const exhaustiveCheck: never = frameOp
throw new Error(`Unknown frame op: ${exhaustiveCheck}`)
}
}
}
export class MessageFrame<T = Record<string, unknown>> extends Frame {
header: MessageFrameHeader
body: T
constructor(body: T, opts?: { type?: string }) {
super()
this.header =
opts?.type !== undefined
? { op: FrameType.Message, t: opts?.type }
: { op: FrameType.Message }
this.body = body
}
get type() {
return this.header.t
}
}
export class ErrorFrame<T extends string = string> extends Frame {
header: ErrorFrameHeader
body: ErrorFrameBody<T>
constructor(body: ErrorFrameBody<T>) {
super()
this.header = { op: FrameType.Error }
this.body = body
}
get code() {
return this.body.error
}
get message() {
return this.body.message
}
}
const kUnset = Symbol('unset')