137 lines
3.7 KiB
TypeScript
137 lines
3.7 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
BrowserScreencastOpcode,
|
|
decodeBrowserScreencastFrame,
|
|
encodeBrowserScreencastFrame
|
|
} from './browser-screencast-protocol'
|
|
|
|
describe('browser screencast binary protocol', () => {
|
|
it('round-trips frame metadata and image bytes', () => {
|
|
const encoded = encodeBrowserScreencastFrame({
|
|
opcode: BrowserScreencastOpcode.Frame,
|
|
seq: 42,
|
|
format: 'jpeg',
|
|
metadata: {
|
|
deviceWidth: 1280,
|
|
deviceHeight: 720,
|
|
pageScaleFactor: 1,
|
|
timestamp: 123
|
|
},
|
|
image: new Uint8Array([1, 2, 3, 4])
|
|
})
|
|
|
|
const decoded = decodeBrowserScreencastFrame(encoded)
|
|
|
|
expect(decoded).toEqual({
|
|
opcode: BrowserScreencastOpcode.Frame,
|
|
seq: 42,
|
|
format: 'jpeg',
|
|
metadata: {
|
|
deviceWidth: 1280,
|
|
deviceHeight: 720,
|
|
pageScaleFactor: 1,
|
|
timestamp: 123
|
|
},
|
|
image: new Uint8Array([1, 2, 3, 4])
|
|
})
|
|
})
|
|
|
|
it('rejects unrelated binary frames', () => {
|
|
expect(decodeBrowserScreencastFrame(new Uint8Array([0, 1, 2, 3]))).toBeNull()
|
|
})
|
|
|
|
it.each([
|
|
{ name: 'version', offset: 1, value: 2 },
|
|
{ name: 'opcode', offset: 2, value: 9 },
|
|
{ name: 'format', offset: 3, value: 9 }
|
|
])('rejects frames with an unsupported $name byte', ({ offset, value }) => {
|
|
const encoded = encodeBrowserScreencastFrame({
|
|
opcode: BrowserScreencastOpcode.Frame,
|
|
seq: 1,
|
|
format: 'jpeg',
|
|
metadata: {},
|
|
image: new Uint8Array([1])
|
|
})
|
|
encoded[offset] = value
|
|
|
|
expect(decodeBrowserScreencastFrame(encoded)).toBeNull()
|
|
})
|
|
|
|
it('rejects frames whose metadata length exceeds the payload', () => {
|
|
const encoded = encodeBrowserScreencastFrame({
|
|
opcode: BrowserScreencastOpcode.Frame,
|
|
seq: 1,
|
|
format: 'jpeg',
|
|
metadata: {},
|
|
image: new Uint8Array([1])
|
|
})
|
|
new DataView(encoded.buffer, encoded.byteOffset, encoded.byteLength).setUint32(
|
|
8,
|
|
encoded.byteLength,
|
|
true
|
|
)
|
|
|
|
expect(decodeBrowserScreencastFrame(encoded)).toBeNull()
|
|
})
|
|
|
|
it('rejects frames with nonzero reserved header bytes', () => {
|
|
const encoded = encodeBrowserScreencastFrame({
|
|
opcode: BrowserScreencastOpcode.Frame,
|
|
seq: 1,
|
|
format: 'jpeg',
|
|
metadata: {},
|
|
image: new Uint8Array([1])
|
|
})
|
|
encoded[12] = 1
|
|
|
|
expect(decodeBrowserScreencastFrame(encoded)).toBeNull()
|
|
})
|
|
|
|
it('rejects non-object metadata', () => {
|
|
const encoded = encodeBrowserScreencastFrame({
|
|
opcode: BrowserScreencastOpcode.Frame,
|
|
seq: 1,
|
|
format: 'jpeg',
|
|
metadata: [] as unknown as Record<string, never>,
|
|
image: new Uint8Array([1])
|
|
})
|
|
|
|
expect(decodeBrowserScreencastFrame(encoded)).toBeNull()
|
|
})
|
|
|
|
it('keeps only finite numeric metadata fields', () => {
|
|
const encoded = encodeBrowserScreencastFrame({
|
|
opcode: BrowserScreencastOpcode.Frame,
|
|
seq: 1,
|
|
format: 'jpeg',
|
|
metadata: {
|
|
deviceWidth: '1280',
|
|
deviceHeight: 720,
|
|
pageScaleFactor: Number.NaN,
|
|
scrollOffsetX: 15,
|
|
extra: 42
|
|
} as unknown as Record<string, never>,
|
|
image: new Uint8Array([1])
|
|
})
|
|
|
|
expect(decodeBrowserScreencastFrame(encoded)?.metadata).toEqual({
|
|
deviceHeight: 720,
|
|
scrollOffsetX: 15
|
|
})
|
|
})
|
|
|
|
it('decodes image bytes as a view over the original frame buffer', () => {
|
|
const encoded = encodeBrowserScreencastFrame({
|
|
opcode: BrowserScreencastOpcode.Frame,
|
|
seq: 1,
|
|
format: 'jpeg',
|
|
metadata: {},
|
|
image: new Uint8Array([7, 8, 9])
|
|
})
|
|
|
|
const decoded = decodeBrowserScreencastFrame(encoded)
|
|
|
|
expect(decoded?.image.buffer).toBe(encoded.buffer)
|
|
})
|
|
})
|