feat: protobuf input messaging (#165)

Replace json protocol by protobuf
generate protobuf files with `bun buf generate` or just `buf generate`

- [x]  Implement all datatypes with proto files

- [x] Map to ts types or use the generated proto types directly with:
   - [x] web frontend
   - [x] relay
   - [x] runner

- [ ] final performance test (to be done when CI builds new images)

---------

Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com>
This commit is contained in:
Philipp Neumann
2025-01-28 15:04:20 +01:00
committed by GitHub
parent 431733a0e8
commit fbaa8835a3
38 changed files with 2758 additions and 647 deletions

654
Cargo.lock generated

File diff suppressed because it is too large Load Diff

17
buf.gen.yaml Normal file
View File

@@ -0,0 +1,17 @@
version: v2
inputs:
- directory: protobufs
plugins:
# TypeScript (frontend)
- remote: buf.build/bufbuild/es
out: packages/input/src/proto
opt: target=ts
# Golang (relay)
- remote: buf.build/protocolbuffers/go
out: packages/relay/internal/proto
opt: paths=source_relative
# Rust (nestri-server)
- remote: buf.build/community/neoeinstein-prost
out: packages/server/src/proto

BIN
bun.lockb

Binary file not shown.

View File

@@ -5,5 +5,13 @@
"sideEffects": false,
"exports": {
".": "./src/index.ts"
},
"devDependencies": {
"@bufbuild/buf": "^1.50.0",
"@bufbuild/protoc-gen-es": "^2.2.3"
},
"dependencies": {
"@bufbuild/protobuf": "^2.2.3",
"protobuf": "^0.11.1"
}
}

View File

@@ -1,8 +1,17 @@
import {type Input} from "./types"
import {keyCodeToLinuxEventCode} from "./codes"
import {MessageInput, encodeMessage} from "./messages";
import {WebRTCStream} from "./webrtc-stream";
import {LatencyTracker} from "./latency";
import {ProtoLatencyTracker, ProtoTimestampEntry} from "./proto/latency_tracker_pb";
import {timestampFromDate} from "@bufbuild/protobuf/wkt";
import {ProtoMessageBase, ProtoMessageInput, ProtoMessageInputSchema} from "./proto/messages_pb";
import {
ProtoInput,
ProtoInputSchema,
ProtoKeyDownSchema,
ProtoKeyUpSchema,
ProtoMouseMoveSchema
} from "./proto/types_pb";
import {create, toBinary} from "@bufbuild/protobuf";
interface Props {
webrtc: WebRTCStream;
@@ -15,19 +24,31 @@ export class Keyboard {
protected connected!: boolean;
// Store references to event listeners
private keydownListener: (e: KeyboardEvent) => void;
private keyupListener: (e: KeyboardEvent) => void;
private readonly keydownListener: (e: KeyboardEvent) => void;
private readonly keyupListener: (e: KeyboardEvent) => void;
constructor({webrtc, canvas}: Props) {
this.wrtc = webrtc;
this.canvas = canvas;
this.keydownListener = this.createKeyboardListener("keydown", (e: any) => ({
type: "KeyDown",
key: this.keyToVirtualKeyCode(e.code)
this.keydownListener = this.createKeyboardListener((e: any) => create(ProtoInputSchema, {
$typeName: "proto.ProtoInput",
inputType: {
case: "keyDown",
value: create(ProtoKeyDownSchema, {
type: "KeyDown",
key: this.keyToVirtualKeyCode(e.code)
}),
}
}));
this.keyupListener = this.createKeyboardListener("keyup", (e: any) => ({
type: "KeyUp",
key: this.keyToVirtualKeyCode(e.code)
this.keyupListener = this.createKeyboardListener((e: any) => create(ProtoInputSchema, {
$typeName: "proto.ProtoInput",
inputType: {
case: "keyUp",
value: create(ProtoKeyUpSchema, {
type: "KeyUp",
key: this.keyToVirtualKeyCode(e.code)
}),
}
}));
this.run()
}
@@ -59,7 +80,7 @@ export class Keyboard {
}
// Helper function to create and return mouse listeners
private createKeyboardListener(type: string, dataCreator: (e: Event) => Partial<Input>): (e: Event) => void {
private createKeyboardListener(dataCreator: (e: Event) => ProtoInput): (e: Event) => void {
return (e: Event) => {
e.preventDefault();
e.stopPropagation();
@@ -67,18 +88,34 @@ export class Keyboard {
if ((e as any).repeat)
return;
const data = dataCreator(e as any); // type assertion because of the way dataCreator is used
const dataString = JSON.stringify({...data, type} as Input);
const data = dataCreator(e as any);
// Latency tracking
const tracker = new LatencyTracker("input-keyboard");
tracker.addTimestamp("client_send");
const message: MessageInput = {
payload_type: "input",
data: dataString,
latency: tracker,
const protoTracker: ProtoLatencyTracker = {
$typeName: "proto.ProtoLatencyTracker",
sequenceId: tracker.sequence_id,
timestamps: [],
};
this.wrtc.sendBinary(encodeMessage(message));
for (const t of tracker.timestamps) {
protoTracker.timestamps.push({
$typeName: "proto.ProtoTimestampEntry",
stage: t.stage,
time: timestampFromDate(t.time),
} as ProtoTimestampEntry);
}
const message: ProtoMessageInput = {
$typeName: "proto.ProtoMessageInput",
messageBase: {
$typeName: "proto.ProtoMessageBase",
payloadType: "input",
latency: protoTracker,
} as ProtoMessageBase,
data: data,
};
this.wrtc.sendBinary(toBinary(ProtoMessageInputSchema, message));
};
}

View File

@@ -6,12 +6,10 @@ type TimestampEntry = {
export class LatencyTracker {
sequence_id: string;
timestamps: TimestampEntry[];
metadata?: Record<string, any>;
constructor(sequence_id: string, timestamps: TimestampEntry[] = [], metadata: Record<string, any> = {}) {
constructor(sequence_id: string, timestamps: TimestampEntry[] = []) {
this.sequence_id = sequence_id;
this.timestamps = timestamps;
this.metadata = metadata;
}
addTimestamp(stage: string): void {
@@ -40,7 +38,6 @@ export class LatencyTracker {
// Fill nanoseconds with zeros to match the expected format
time: entry.time.toISOString().replace(/\.(\d+)Z$/, ".$1000000Z"),
})),
metadata: this.metadata,
};
}
@@ -49,6 +46,6 @@ export class LatencyTracker {
stage: ts.stage,
time: new Date(ts.time),
}));
return new LatencyTracker(json.sequence_id, timestamps, json.metadata);
return new LatencyTracker(json.sequence_id, timestamps);
}
}

View File

@@ -1,13 +1,7 @@
import {gzip, ungzip} from "pako";
import {LatencyTracker} from "./latency";
export interface MessageBase {
payload_type: string;
}
export interface MessageInput extends MessageBase {
payload_type: "input";
data: string;
latency?: LatencyTracker;
}
@@ -41,33 +35,3 @@ export interface MessageAnswer extends MessageBase {
payload_type: "answer";
answer_type: AnswerType;
}
function blobToUint8Array(blob: Blob): Promise<Uint8Array> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => {
const arrayBuffer = reader.result as ArrayBuffer;
resolve(new Uint8Array(arrayBuffer));
};
reader.onerror = reject;
reader.readAsArrayBuffer(blob);
});
}
export function encodeMessage<T>(message: T): Uint8Array {
// Convert the message to JSON string
const json = JSON.stringify(message);
// Compress the JSON string using gzip
return gzip(json);
}
export async function decodeMessage<T>(data: Blob): Promise<T> {
// Convert the Blob to Uint8Array
const array = await blobToUint8Array(data);
// Decompress the gzip data
const decompressed = ungzip(array);
// Convert the Uint8Array to JSON string
const json = new TextDecoder().decode(decompressed);
// Parse the JSON string
return JSON.parse(json);
}

View File

@@ -1,8 +1,18 @@
import {type Input} from "./types"
import {mouseButtonToLinuxEventCode} from "./codes"
import {MessageInput, encodeMessage} from "./messages";
import {WebRTCStream} from "./webrtc-stream";
import {LatencyTracker} from "./latency";
import {ProtoMessageInput, ProtoMessageBase, ProtoMessageInputSchema} from "./proto/messages_pb";
import {
ProtoInput, ProtoInputSchema,
ProtoMouseKeyDown, ProtoMouseKeyDownSchema,
ProtoMouseKeyUp, ProtoMouseKeyUpSchema,
ProtoMouseMove,
ProtoMouseMoveSchema,
ProtoMouseWheel, ProtoMouseWheelSchema
} from "./proto/types_pb";
import {mouseButtonToLinuxEventCode} from "./codes";
import {ProtoLatencyTracker, ProtoTimestampEntry} from "./proto/latency_tracker_pb";
import {create, toBinary} from "@bufbuild/protobuf";
import {timestampFromDate} from "@bufbuild/protobuf/wkt";
interface Props {
webrtc: WebRTCStream;
@@ -15,33 +25,56 @@ export class Mouse {
protected connected!: boolean;
// Store references to event listeners
private mousemoveListener: (e: MouseEvent) => void;
private mousedownListener: (e: MouseEvent) => void;
private mouseupListener: (e: MouseEvent) => void;
private mousewheelListener: (e: WheelEvent) => void;
private readonly mousemoveListener: (e: MouseEvent) => void;
private readonly mousedownListener: (e: MouseEvent) => void;
private readonly mouseupListener: (e: MouseEvent) => void;
private readonly mousewheelListener: (e: WheelEvent) => void;
constructor({webrtc, canvas}: Props) {
this.wrtc = webrtc;
this.canvas = canvas;
this.mousemoveListener = this.createMouseListener("mousemove", (e: any) => ({
type: "MouseMove",
x: e.movementX,
y: e.movementY
this.mousemoveListener = this.createMouseListener((e: any) => create(ProtoInputSchema, {
$typeName: "proto.ProtoInput",
inputType: {
case: "mouseMove",
value: create(ProtoMouseMoveSchema, {
type: "MouseMove",
x: e.movementX,
y: e.movementY
}),
}
}));
this.mousedownListener = this.createMouseListener("mousedown", (e: any) => ({
type: "MouseKeyDown",
key: this.keyToVirtualKeyCode(e.button)
this.mousedownListener = this.createMouseListener((e: any) => create(ProtoInputSchema, {
$typeName: "proto.ProtoInput",
inputType: {
case: "mouseKeyDown",
value: create(ProtoMouseKeyDownSchema, {
type: "MouseKeyDown",
key: this.keyToVirtualKeyCode(e.button)
}),
}
}));
this.mouseupListener = this.createMouseListener("mouseup", (e: any) => ({
type: "MouseKeyUp",
key: this.keyToVirtualKeyCode(e.button)
this.mouseupListener = this.createMouseListener((e: any) => create(ProtoInputSchema, {
$typeName: "proto.ProtoInput",
inputType: {
case: "mouseKeyUp",
value: create(ProtoMouseKeyUpSchema, {
type: "MouseKeyUp",
key: this.keyToVirtualKeyCode(e.button)
}),
}
}));
this.mousewheelListener = this.createMouseListener("wheel", (e: any) => ({
type: "MouseWheel",
x: e.deltaX,
y: e.deltaY
this.mousewheelListener = this.createMouseListener((e: any) => create(ProtoInputSchema, {
$typeName: "proto.ProtoInput",
inputType: {
case: "mouseWheel",
value: create(ProtoMouseWheelSchema, {
type: "MouseWheel",
x: e.deltaX,
y: e.deltaY
}),
}
}));
this.run()
@@ -59,10 +92,10 @@ export class Mouse {
if (document.pointerLockElement == this.canvas) {
this.connected = true
this.canvas.addEventListener("mousemove", this.mousemoveListener, { passive: false });
this.canvas.addEventListener("mousedown", this.mousedownListener, { passive: false });
this.canvas.addEventListener("mouseup", this.mouseupListener, { passive: false });
this.canvas.addEventListener("wheel", this.mousewheelListener, { passive: false });
this.canvas.addEventListener("mousemove", this.mousemoveListener, {passive: false});
this.canvas.addEventListener("mousedown", this.mousedownListener, {passive: false});
this.canvas.addEventListener("mouseup", this.mouseupListener, {passive: false});
this.canvas.addEventListener("wheel", this.mousewheelListener, {passive: false});
} else {
if (this.connected) {
@@ -81,22 +114,38 @@ export class Mouse {
}
// Helper function to create and return mouse listeners
private createMouseListener(type: string, dataCreator: (e: Event) => Partial<Input>): (e: Event) => void {
private createMouseListener(dataCreator: (e: Event) => ProtoInput): (e: Event) => void {
return (e: Event) => {
e.preventDefault();
e.stopPropagation();
const data = dataCreator(e as any); // type assertion because of the way dataCreator is used
const dataString = JSON.stringify({...data, type} as Input);
const data = dataCreator(e as any);
// Latency tracking
const tracker = new LatencyTracker("input-mouse");
tracker.addTimestamp("client_send");
const message: MessageInput = {
payload_type: "input",
data: dataString,
latency: tracker,
const protoTracker: ProtoLatencyTracker = {
$typeName: "proto.ProtoLatencyTracker",
sequenceId: tracker.sequence_id,
timestamps: [],
};
this.wrtc.sendBinary(encodeMessage(message));
for (const t of tracker.timestamps) {
protoTracker.timestamps.push({
$typeName: "proto.ProtoTimestampEntry",
stage: t.stage,
time: timestampFromDate(t.time),
} as ProtoTimestampEntry);
}
const message: ProtoMessageInput = {
$typeName: "proto.ProtoMessageInput",
messageBase: {
$typeName: "proto.ProtoMessageBase",
payloadType: "input",
latency: protoTracker,
} as ProtoMessageBase,
data: data,
};
this.wrtc.sendBinary(toBinary(ProtoMessageInputSchema, message));
};
}

View File

@@ -0,0 +1,60 @@
// @generated by protoc-gen-es v2.2.3 with parameter "target=ts"
// @generated from file latency_tracker.proto (package proto, syntax proto3)
/* eslint-disable */
import type { GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1";
import { fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1";
import type { Timestamp } from "@bufbuild/protobuf/wkt";
import { file_google_protobuf_timestamp } from "@bufbuild/protobuf/wkt";
import type { Message } from "@bufbuild/protobuf";
/**
* Describes the file latency_tracker.proto.
*/
export const file_latency_tracker: GenFile = /*@__PURE__*/
fileDesc("ChVsYXRlbmN5X3RyYWNrZXIucHJvdG8SBXByb3RvIk4KE1Byb3RvVGltZXN0YW1wRW50cnkSDQoFc3RhZ2UYASABKAkSKAoEdGltZRgCIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAiWgoTUHJvdG9MYXRlbmN5VHJhY2tlchITCgtzZXF1ZW5jZV9pZBgBIAEoCRIuCgp0aW1lc3RhbXBzGAIgAygLMhoucHJvdG8uUHJvdG9UaW1lc3RhbXBFbnRyeUIWWhRyZWxheS9pbnRlcm5hbC9wcm90b2IGcHJvdG8z", [file_google_protobuf_timestamp]);
/**
* @generated from message proto.ProtoTimestampEntry
*/
export type ProtoTimestampEntry = Message<"proto.ProtoTimestampEntry"> & {
/**
* @generated from field: string stage = 1;
*/
stage: string;
/**
* @generated from field: google.protobuf.Timestamp time = 2;
*/
time?: Timestamp;
};
/**
* Describes the message proto.ProtoTimestampEntry.
* Use `create(ProtoTimestampEntrySchema)` to create a new message.
*/
export const ProtoTimestampEntrySchema: GenMessage<ProtoTimestampEntry> = /*@__PURE__*/
messageDesc(file_latency_tracker, 0);
/**
* @generated from message proto.ProtoLatencyTracker
*/
export type ProtoLatencyTracker = Message<"proto.ProtoLatencyTracker"> & {
/**
* @generated from field: string sequence_id = 1;
*/
sequenceId: string;
/**
* @generated from field: repeated proto.ProtoTimestampEntry timestamps = 2;
*/
timestamps: ProtoTimestampEntry[];
};
/**
* Describes the message proto.ProtoLatencyTracker.
* Use `create(ProtoLatencyTrackerSchema)` to create a new message.
*/
export const ProtoLatencyTrackerSchema: GenMessage<ProtoLatencyTracker> = /*@__PURE__*/
messageDesc(file_latency_tracker, 1);

View File

@@ -0,0 +1,62 @@
// @generated by protoc-gen-es v2.2.3 with parameter "target=ts"
// @generated from file messages.proto (package proto, syntax proto3)
/* eslint-disable */
import type { GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1";
import { fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1";
import type { ProtoInput } from "./types_pb";
import { file_types } from "./types_pb";
import type { ProtoLatencyTracker } from "./latency_tracker_pb";
import { file_latency_tracker } from "./latency_tracker_pb";
import type { Message } from "@bufbuild/protobuf";
/**
* Describes the file messages.proto.
*/
export const file_messages: GenFile = /*@__PURE__*/
fileDesc("Cg5tZXNzYWdlcy5wcm90bxIFcHJvdG8iVQoQUHJvdG9NZXNzYWdlQmFzZRIUCgxwYXlsb2FkX3R5cGUYASABKAkSKwoHbGF0ZW5jeRgCIAEoCzIaLnByb3RvLlByb3RvTGF0ZW5jeVRyYWNrZXIiYwoRUHJvdG9NZXNzYWdlSW5wdXQSLQoMbWVzc2FnZV9iYXNlGAEgASgLMhcucHJvdG8uUHJvdG9NZXNzYWdlQmFzZRIfCgRkYXRhGAIgASgLMhEucHJvdG8uUHJvdG9JbnB1dEIWWhRyZWxheS9pbnRlcm5hbC9wcm90b2IGcHJvdG8z", [file_types, file_latency_tracker]);
/**
* @generated from message proto.ProtoMessageBase
*/
export type ProtoMessageBase = Message<"proto.ProtoMessageBase"> & {
/**
* @generated from field: string payload_type = 1;
*/
payloadType: string;
/**
* @generated from field: proto.ProtoLatencyTracker latency = 2;
*/
latency?: ProtoLatencyTracker;
};
/**
* Describes the message proto.ProtoMessageBase.
* Use `create(ProtoMessageBaseSchema)` to create a new message.
*/
export const ProtoMessageBaseSchema: GenMessage<ProtoMessageBase> = /*@__PURE__*/
messageDesc(file_messages, 0);
/**
* @generated from message proto.ProtoMessageInput
*/
export type ProtoMessageInput = Message<"proto.ProtoMessageInput"> & {
/**
* @generated from field: proto.ProtoMessageBase message_base = 1;
*/
messageBase?: ProtoMessageBase;
/**
* @generated from field: proto.ProtoInput data = 2;
*/
data?: ProtoInput;
};
/**
* Describes the message proto.ProtoMessageInput.
* Use `create(ProtoMessageInputSchema)` to create a new message.
*/
export const ProtoMessageInputSchema: GenMessage<ProtoMessageInput> = /*@__PURE__*/
messageDesc(file_messages, 1);

View File

@@ -0,0 +1,272 @@
// @generated by protoc-gen-es v2.2.3 with parameter "target=ts"
// @generated from file types.proto (package proto, syntax proto3)
/* eslint-disable */
import type { GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1";
import { fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1";
import type { Message } from "@bufbuild/protobuf";
/**
* Describes the file types.proto.
*/
export const file_types: GenFile = /*@__PURE__*/
fileDesc("Cgt0eXBlcy5wcm90bxIFcHJvdG8iNAoOUHJvdG9Nb3VzZU1vdmUSDAoEdHlwZRgBIAEoCRIJCgF4GAIgASgFEgkKAXkYAyABKAUiNwoRUHJvdG9Nb3VzZU1vdmVBYnMSDAoEdHlwZRgBIAEoCRIJCgF4GAIgASgFEgkKAXkYAyABKAUiNQoPUHJvdG9Nb3VzZVdoZWVsEgwKBHR5cGUYASABKAkSCQoBeBgCIAEoBRIJCgF5GAMgASgFIi4KEVByb3RvTW91c2VLZXlEb3duEgwKBHR5cGUYASABKAkSCwoDa2V5GAIgASgFIiwKD1Byb3RvTW91c2VLZXlVcBIMCgR0eXBlGAEgASgJEgsKA2tleRgCIAEoBSIpCgxQcm90b0tleURvd24SDAoEdHlwZRgBIAEoCRILCgNrZXkYAiABKAUiJwoKUHJvdG9LZXlVcBIMCgR0eXBlGAEgASgJEgsKA2tleRgCIAEoBSLcAgoKUHJvdG9JbnB1dBIrCgptb3VzZV9tb3ZlGAEgASgLMhUucHJvdG8uUHJvdG9Nb3VzZU1vdmVIABIyCg5tb3VzZV9tb3ZlX2FicxgCIAEoCzIYLnByb3RvLlByb3RvTW91c2VNb3ZlQWJzSAASLQoLbW91c2Vfd2hlZWwYAyABKAsyFi5wcm90by5Qcm90b01vdXNlV2hlZWxIABIyCg5tb3VzZV9rZXlfZG93bhgEIAEoCzIYLnByb3RvLlByb3RvTW91c2VLZXlEb3duSAASLgoMbW91c2Vfa2V5X3VwGAUgASgLMhYucHJvdG8uUHJvdG9Nb3VzZUtleVVwSAASJwoIa2V5X2Rvd24YBiABKAsyEy5wcm90by5Qcm90b0tleURvd25IABIjCgZrZXlfdXAYByABKAsyES5wcm90by5Qcm90b0tleVVwSABCDAoKaW5wdXRfdHlwZUIWWhRyZWxheS9pbnRlcm5hbC9wcm90b2IGcHJvdG8z");
/**
* MouseMove message
*
* @generated from message proto.ProtoMouseMove
*/
export type ProtoMouseMove = Message<"proto.ProtoMouseMove"> & {
/**
* Fixed value "MouseMove"
*
* @generated from field: string type = 1;
*/
type: string;
/**
* @generated from field: int32 x = 2;
*/
x: number;
/**
* @generated from field: int32 y = 3;
*/
y: number;
};
/**
* Describes the message proto.ProtoMouseMove.
* Use `create(ProtoMouseMoveSchema)` to create a new message.
*/
export const ProtoMouseMoveSchema: GenMessage<ProtoMouseMove> = /*@__PURE__*/
messageDesc(file_types, 0);
/**
* MouseMoveAbs message
*
* @generated from message proto.ProtoMouseMoveAbs
*/
export type ProtoMouseMoveAbs = Message<"proto.ProtoMouseMoveAbs"> & {
/**
* Fixed value "MouseMoveAbs"
*
* @generated from field: string type = 1;
*/
type: string;
/**
* @generated from field: int32 x = 2;
*/
x: number;
/**
* @generated from field: int32 y = 3;
*/
y: number;
};
/**
* Describes the message proto.ProtoMouseMoveAbs.
* Use `create(ProtoMouseMoveAbsSchema)` to create a new message.
*/
export const ProtoMouseMoveAbsSchema: GenMessage<ProtoMouseMoveAbs> = /*@__PURE__*/
messageDesc(file_types, 1);
/**
* MouseWheel message
*
* @generated from message proto.ProtoMouseWheel
*/
export type ProtoMouseWheel = Message<"proto.ProtoMouseWheel"> & {
/**
* Fixed value "MouseWheel"
*
* @generated from field: string type = 1;
*/
type: string;
/**
* @generated from field: int32 x = 2;
*/
x: number;
/**
* @generated from field: int32 y = 3;
*/
y: number;
};
/**
* Describes the message proto.ProtoMouseWheel.
* Use `create(ProtoMouseWheelSchema)` to create a new message.
*/
export const ProtoMouseWheelSchema: GenMessage<ProtoMouseWheel> = /*@__PURE__*/
messageDesc(file_types, 2);
/**
* MouseKeyDown message
*
* @generated from message proto.ProtoMouseKeyDown
*/
export type ProtoMouseKeyDown = Message<"proto.ProtoMouseKeyDown"> & {
/**
* Fixed value "MouseKeyDown"
*
* @generated from field: string type = 1;
*/
type: string;
/**
* @generated from field: int32 key = 2;
*/
key: number;
};
/**
* Describes the message proto.ProtoMouseKeyDown.
* Use `create(ProtoMouseKeyDownSchema)` to create a new message.
*/
export const ProtoMouseKeyDownSchema: GenMessage<ProtoMouseKeyDown> = /*@__PURE__*/
messageDesc(file_types, 3);
/**
* MouseKeyUp message
*
* @generated from message proto.ProtoMouseKeyUp
*/
export type ProtoMouseKeyUp = Message<"proto.ProtoMouseKeyUp"> & {
/**
* Fixed value "MouseKeyUp"
*
* @generated from field: string type = 1;
*/
type: string;
/**
* @generated from field: int32 key = 2;
*/
key: number;
};
/**
* Describes the message proto.ProtoMouseKeyUp.
* Use `create(ProtoMouseKeyUpSchema)` to create a new message.
*/
export const ProtoMouseKeyUpSchema: GenMessage<ProtoMouseKeyUp> = /*@__PURE__*/
messageDesc(file_types, 4);
/**
* KeyDown message
*
* @generated from message proto.ProtoKeyDown
*/
export type ProtoKeyDown = Message<"proto.ProtoKeyDown"> & {
/**
* Fixed value "KeyDown"
*
* @generated from field: string type = 1;
*/
type: string;
/**
* @generated from field: int32 key = 2;
*/
key: number;
};
/**
* Describes the message proto.ProtoKeyDown.
* Use `create(ProtoKeyDownSchema)` to create a new message.
*/
export const ProtoKeyDownSchema: GenMessage<ProtoKeyDown> = /*@__PURE__*/
messageDesc(file_types, 5);
/**
* KeyUp message
*
* @generated from message proto.ProtoKeyUp
*/
export type ProtoKeyUp = Message<"proto.ProtoKeyUp"> & {
/**
* Fixed value "KeyUp"
*
* @generated from field: string type = 1;
*/
type: string;
/**
* @generated from field: int32 key = 2;
*/
key: number;
};
/**
* Describes the message proto.ProtoKeyUp.
* Use `create(ProtoKeyUpSchema)` to create a new message.
*/
export const ProtoKeyUpSchema: GenMessage<ProtoKeyUp> = /*@__PURE__*/
messageDesc(file_types, 6);
/**
* Union of all Input types
*
* @generated from message proto.ProtoInput
*/
export type ProtoInput = Message<"proto.ProtoInput"> & {
/**
* @generated from oneof proto.ProtoInput.input_type
*/
inputType: {
/**
* @generated from field: proto.ProtoMouseMove mouse_move = 1;
*/
value: ProtoMouseMove;
case: "mouseMove";
} | {
/**
* @generated from field: proto.ProtoMouseMoveAbs mouse_move_abs = 2;
*/
value: ProtoMouseMoveAbs;
case: "mouseMoveAbs";
} | {
/**
* @generated from field: proto.ProtoMouseWheel mouse_wheel = 3;
*/
value: ProtoMouseWheel;
case: "mouseWheel";
} | {
/**
* @generated from field: proto.ProtoMouseKeyDown mouse_key_down = 4;
*/
value: ProtoMouseKeyDown;
case: "mouseKeyDown";
} | {
/**
* @generated from field: proto.ProtoMouseKeyUp mouse_key_up = 5;
*/
value: ProtoMouseKeyUp;
case: "mouseKeyUp";
} | {
/**
* @generated from field: proto.ProtoKeyDown key_down = 6;
*/
value: ProtoKeyDown;
case: "keyDown";
} | {
/**
* @generated from field: proto.ProtoKeyUp key_up = 7;
*/
value: ProtoKeyUp;
case: "keyUp";
} | { case: undefined; value?: undefined };
};
/**
* Describes the message proto.ProtoInput.
* Use `create(ProtoInputSchema)` to create a new message.
*/
export const ProtoInputSchema: GenMessage<ProtoInput> = /*@__PURE__*/
messageDesc(file_types, 7);

View File

@@ -1,52 +0,0 @@
interface BaseInput {
timestamp?: number; // Add a timestamp for better context (optional)
}
interface MouseMove extends BaseInput {
type: "MouseMove";
x: number;
y: number;
}
interface MouseMoveAbs extends BaseInput {
type: "MouseMoveAbs";
x: number;
y: number;
}
interface MouseWheel extends BaseInput {
type: "MouseWheel";
x: number;
y: number;
}
interface MouseKeyDown extends BaseInput {
type: "MouseKeyDown";
key: number;
}
interface MouseKeyUp extends BaseInput {
type: "MouseKeyUp";
key: number;
}
interface KeyDown extends BaseInput {
type: "KeyDown";
key: number;
}
interface KeyUp extends BaseInput {
type: "KeyUp";
key: number;
}
export type Input =
| MouseMove
| MouseMoveAbs
| MouseWheel
| MouseKeyDown
| MouseKeyUp
| KeyDown
| KeyUp;

View File

@@ -6,8 +6,6 @@ import {
MessageAnswer,
JoinerType,
AnswerType,
decodeMessage,
encodeMessage
} from "./messages";
export class WebRTCStream {
@@ -40,16 +38,16 @@ export class WebRTCStream {
payload_type: "join",
joiner_type: JoinerType.JoinerClient
};
this._ws!.send(encodeMessage(joinMessage));
this._ws!.send(JSON.stringify(joinMessage));
}
let iceHolder: RTCIceCandidateInit[] = [];
this._ws.onmessage = async (e) => {
// allow only binary
if (typeof e.data !== "object") return;
// allow only JSON
if (typeof e.data === "object") return;
if (!e.data) return;
const message = await decodeMessage<MessageBase>(e.data);
const message = JSON.parse(e.data) as MessageBase;
switch (message.payload_type) {
case "sdp":
if (!this._pc) {
@@ -63,7 +61,7 @@ export class WebRTCStream {
// Force stereo in Chromium browsers
answer.sdp = this.forceOpusStereo(answer.sdp!);
await this._pc!.setLocalDescription(answer);
this._ws!.send(encodeMessage({
this._ws!.send(JSON.stringify({
payload_type: "sdp",
sdp: answer
}));
@@ -154,7 +152,7 @@ export class WebRTCStream {
payload_type: "ice",
candidate: e.candidate
};
this._ws!.send(encodeMessage(message));
this._ws!.send(JSON.stringify(message));
}
}

View File

@@ -6,26 +6,27 @@ require (
github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.3
github.com/pion/interceptor v0.1.37
github.com/pion/webrtc/v4 v4.0.2
github.com/pion/webrtc/v4 v4.0.8
google.golang.org/protobuf v1.36.4
)
require (
github.com/pion/datachannel v1.5.9 // indirect
github.com/pion/datachannel v1.5.10 // indirect
github.com/pion/dtls/v3 v3.0.4 // indirect
github.com/pion/ice/v4 v4.0.2 // indirect
github.com/pion/logging v0.2.2 // indirect
github.com/pion/ice/v4 v4.0.5 // indirect
github.com/pion/logging v0.2.3 // indirect
github.com/pion/mdns/v2 v2.0.7 // indirect
github.com/pion/randutil v0.1.0 // indirect
github.com/pion/rtcp v1.2.14 // indirect
github.com/pion/rtp v1.8.9 // indirect
github.com/pion/sctp v1.8.34 // indirect
github.com/pion/sdp/v3 v3.0.9 // indirect
github.com/pion/rtcp v1.2.15 // indirect
github.com/pion/rtp v1.8.11 // indirect
github.com/pion/sctp v1.8.35 // indirect
github.com/pion/sdp/v3 v3.0.10 // indirect
github.com/pion/srtp/v3 v3.0.4 // indirect
github.com/pion/stun/v3 v3.0.0 // indirect
github.com/pion/transport/v3 v3.0.7 // indirect
github.com/pion/turn/v4 v4.0.0 // indirect
github.com/wlynxg/anet v0.0.5 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/net v0.31.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/crypto v0.32.0 // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/sys v0.29.0 // indirect
)

View File

@@ -1,32 +1,33 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/pion/datachannel v1.5.9 h1:LpIWAOYPyDrXtU+BW7X0Yt/vGtYxtXQ8ql7dFfYUVZA=
github.com/pion/datachannel v1.5.9/go.mod h1:kDUuk4CU4Uxp82NH4LQZbISULkX/HtzKa4P7ldf9izE=
github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o=
github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M=
github.com/pion/dtls/v3 v3.0.4 h1:44CZekewMzfrn9pmGrj5BNnTMDCFwr+6sLH+cCuLM7U=
github.com/pion/dtls/v3 v3.0.4/go.mod h1:R373CsjxWqNPf6MEkfdy3aSe9niZvL/JaKlGeFphtMg=
github.com/pion/ice/v4 v4.0.2 h1:1JhBRX8iQLi0+TfcavTjPjI6GO41MFn4CeTBX+Y9h5s=
github.com/pion/ice/v4 v4.0.2/go.mod h1:DCdqyzgtsDNYN6/3U8044j3U7qsJ9KFJC92VnOWHvXg=
github.com/pion/ice/v4 v4.0.5 h1:6awVfa1jg9YsI9/Lep4TG/o3kwS1Oayr5b8xz50ibJ8=
github.com/pion/ice/v4 v4.0.5/go.mod h1:JJaoEIxUIlGDA9gaRZbwXYqI3j6VG/QchpjX+QmwN6A=
github.com/pion/interceptor v0.1.37 h1:aRA8Zpab/wE7/c0O3fh1PqY0AJI3fCSEM5lRWJVorwI=
github.com/pion/interceptor v0.1.37/go.mod h1:JzxbJ4umVTlZAf+/utHzNesY8tmRkM2lVmkS82TTj8Y=
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/logging v0.2.3 h1:gHuf0zpoh1GW67Nr6Gj4cv5Z9ZscU7g/EaoC/Ke/igI=
github.com/pion/logging v0.2.3/go.mod h1:z8YfknkquMe1csOrxK5kc+5/ZPAzMxbKLX5aXpbpC90=
github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM=
github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA=
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/rtcp v1.2.14 h1:KCkGV3vJ+4DAJmvP0vaQShsb0xkRfWkO540Gy102KyE=
github.com/pion/rtcp v1.2.14/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4=
github.com/pion/rtp v1.8.9 h1:E2HX740TZKaqdcPmf4pw6ZZuG8u5RlMMt+l3dxeu6Wk=
github.com/pion/rtp v1.8.9/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
github.com/pion/sctp v1.8.34 h1:rCuD3m53i0oGxCSp7FLQKvqVx0Nf5AUAHhMRXTTQjBc=
github.com/pion/sctp v1.8.34/go.mod h1:yWkCClkXlzVW7BXfI2PjrUGBwUI0CjXJBkhLt+sdo4U=
github.com/pion/sdp/v3 v3.0.9 h1:pX++dCHoHUwq43kuwf3PyJfHlwIj4hXA7Vrifiq0IJY=
github.com/pion/sdp/v3 v3.0.9/go.mod h1:B5xmvENq5IXJimIO4zfp6LAe1fD9N+kFv+V/1lOdz8M=
github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo=
github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0=
github.com/pion/rtp v1.8.11 h1:17xjnY5WO5hgO6SD3/NTIUPvSFw/PbLsIJyz1r1yNIk=
github.com/pion/rtp v1.8.11/go.mod h1:8uMBJj32Pa1wwx8Fuv/AsFhn8jsgw+3rUC2PfoBZ8p4=
github.com/pion/sctp v1.8.35 h1:qwtKvNK1Wc5tHMIYgTDJhfZk7vATGVHhXbUDfHbYwzA=
github.com/pion/sctp v1.8.35/go.mod h1:EcXP8zCYVTRy3W9xtOF7wJm1L1aXfKRQzaM33SjQlzg=
github.com/pion/sdp/v3 v3.0.10 h1:6MChLE/1xYB+CjumMw+gZ9ufp2DPApuVSnDT8t5MIgA=
github.com/pion/sdp/v3 v3.0.10/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E=
github.com/pion/srtp/v3 v3.0.4 h1:2Z6vDVxzrX3UHEgrUyIGM4rRouoC7v+NiF1IHtp9B5M=
github.com/pion/srtp/v3 v3.0.4/go.mod h1:1Jx3FwDoxpRaTh1oRV8A/6G1BnFL+QI82eK4ms8EEJQ=
github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw=
@@ -35,28 +36,23 @@ github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1
github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo=
github.com/pion/turn/v4 v4.0.0 h1:qxplo3Rxa9Yg1xXDxxH8xaqcyGUtbHYw4QSCvmFWvhM=
github.com/pion/turn/v4 v4.0.0/go.mod h1:MuPDkm15nYSklKpN8vWJ9W2M0PlyQZqYt1McGuxG7mA=
github.com/pion/webrtc/v4 v4.0.2 h1:fBwm5/hqSUybrCWl0DDBSTDrpbkcgkqpeLmXw9CsBQA=
github.com/pion/webrtc/v4 v4.0.2/go.mod h1:moylBT2A4dNoEaYBCdV1nThM3TLwRHzWszIG+eSPaqQ=
github.com/pion/webrtc/v4 v4.0.8 h1:T1ZmnT9qxIJIt4d8XoiMOBrTClGHDDXNg9e/fh018Qc=
github.com/pion/webrtc/v4 v4.0.8/go.mod h1:HHBeUVBAC+j4ZFnYhovEFStF02Arb1EyD4G7e7HBTJw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -2,20 +2,22 @@ package relay
import (
"github.com/pion/webrtc/v4"
"google.golang.org/protobuf/proto"
"log"
gen "relay/internal/proto"
)
// NestriDataChannel is a custom data channel with callbacks
type NestriDataChannel struct {
*webrtc.DataChannel
binaryCallbacks map[string]OnMessageCallback // MessageBase type -> callback
callbacks map[string]OnMessageCallback // MessageBase type -> callback
}
// NewNestriDataChannel creates a new NestriDataChannel from *webrtc.DataChannel
func NewNestriDataChannel(dc *webrtc.DataChannel) *NestriDataChannel {
ndc := &NestriDataChannel{
DataChannel: dc,
binaryCallbacks: make(map[string]OnMessageCallback),
DataChannel: dc,
callbacks: make(map[string]OnMessageCallback),
}
// Handler for incoming messages
@@ -26,14 +28,14 @@ func NewNestriDataChannel(dc *webrtc.DataChannel) *NestriDataChannel {
}
// Decode message
var base MessageBase
if err := DecodeMessage(msg.Data, &base); err != nil {
var base gen.ProtoMessageInput
if err := proto.Unmarshal(msg.Data, &base); err != nil {
log.Printf("Failed to decode binary DataChannel message, reason: %s\n", err)
return
}
// Handle message type callback
if callback, ok := ndc.binaryCallbacks[base.PayloadType]; ok {
if callback, ok := ndc.callbacks["input"]; ok {
go callback(msg.Data)
} // TODO: Log unknown message type?
})
@@ -48,16 +50,16 @@ func (ndc *NestriDataChannel) SendBinary(data []byte) error {
// RegisterMessageCallback registers a callback for a given binary message type
func (ndc *NestriDataChannel) RegisterMessageCallback(msgType string, callback OnMessageCallback) {
if ndc.binaryCallbacks == nil {
ndc.binaryCallbacks = make(map[string]OnMessageCallback)
if ndc.callbacks == nil {
ndc.callbacks = make(map[string]OnMessageCallback)
}
ndc.binaryCallbacks[msgType] = callback
ndc.callbacks[msgType] = callback
}
// UnregisterMessageCallback removes the callback for a given binary message type
func (ndc *NestriDataChannel) UnregisterMessageCallback(msgType string) {
if ndc.binaryCallbacks != nil {
delete(ndc.binaryCallbacks, msgType)
if ndc.callbacks != nil {
delete(ndc.callbacks, msgType)
}
}

View File

@@ -1,8 +1,11 @@
package relay
import (
"encoding/json"
"github.com/pion/webrtc/v4"
"google.golang.org/protobuf/proto"
"log"
gen "relay/internal/proto"
)
func participantHandler(participant *Participant, room *Room) {
@@ -54,15 +57,22 @@ func participantHandler(participant *Participant, room *Room) {
if room.DataChannel != nil {
// If debug mode, decode and add our timestamp, otherwise just send to room
if GetFlags().Debug {
var inputMsg MessageInput
if err = DecodeMessage(data, &inputMsg); err != nil {
var inputMsg gen.ProtoMessageInput
if err = proto.Unmarshal(data, &inputMsg); err != nil {
log.Printf("Failed to decode input message from participant: '%s' in room: '%s' - reason: %s\n", participant.ID, room.Name, err)
return
}
inputMsg.LatencyTracker.AddTimestamp("relay_to_node")
// Encode and send
if data, err = EncodeMessage(inputMsg); err != nil {
log.Printf("Failed to encode input message for participant: '%s' in room: '%s' - reason: %s\n", participant.ID, room.Name, err)
protoLat := inputMsg.GetMessageBase().GetLatency()
if protoLat != nil {
lat := LatencyTrackerFromProto(protoLat)
lat.AddTimestamp("relay_to_node")
protoLat = lat.ToProto()
}
// Marshal and send
if data, err = proto.Marshal(&inputMsg); err != nil {
log.Printf("Failed to marshal input message for participant: '%s' in room: '%s' - reason: %s\n", participant.ID, room.Name, err)
return
}
if err = room.DataChannel.SendBinary(data); err != nil {
@@ -94,7 +104,7 @@ func participantHandler(participant *Participant, room *Room) {
// ICE callback
participant.WebSocket.RegisterMessageCallback("ice", func(data []byte) {
var iceMsg MessageICECandidate
if err = DecodeMessage(data, &iceMsg); err != nil {
if err = json.Unmarshal(data, &iceMsg); err != nil {
log.Printf("Failed to decode ICE message from participant: '%s' in room: '%s' - reason: %s\n", participant.ID, room.Name, err)
return
}
@@ -120,7 +130,7 @@ func participantHandler(participant *Participant, room *Room) {
// SDP answer callback
participant.WebSocket.RegisterMessageCallback("sdp", func(data []byte) {
var sdpMsg MessageSDP
if err = DecodeMessage(data, &sdpMsg); err != nil {
if err = json.Unmarshal(data, &sdpMsg); err != nil {
log.Printf("Failed to decode SDP message from participant: '%s' in room: '%s' - reason: %s\n", participant.ID, room.Name, err)
return
}
@@ -130,7 +140,7 @@ func participantHandler(participant *Participant, room *Room) {
// Log callback
participant.WebSocket.RegisterMessageCallback("log", func(data []byte) {
var logMsg MessageLog
if err = DecodeMessage(data, &logMsg); err != nil {
if err = json.Unmarshal(data, &logMsg); err != nil {
log.Printf("Failed to decode log message from participant: '%s' in room: '%s' - reason: %s\n", participant.ID, room.Name, err)
return
}

View File

@@ -1,6 +1,7 @@
package relay
import (
"encoding/json"
"github.com/gorilla/websocket"
"log"
"net/http"
@@ -81,7 +82,7 @@ func wsHandler(w http.ResponseWriter, r *http.Request) {
// Assign message handler for join request
ws.RegisterMessageCallback("join", func(data []byte) {
var joinMsg MessageJoin
if err = DecodeMessage(data, &joinMsg); err != nil {
if err = json.Unmarshal(data, &joinMsg); err != nil {
log.Printf("Failed to decode join message: %s\n", err)
return
}

View File

@@ -1,6 +1,7 @@
package relay
import (
"encoding/json"
"errors"
"fmt"
"github.com/pion/webrtc/v4"
@@ -134,7 +135,7 @@ func ingestHandler(room *Room) {
// ICE callback
room.WebSocket.RegisterMessageCallback("ice", func(data []byte) {
var iceMsg MessageICECandidate
if err = DecodeMessage(data, &iceMsg); err != nil {
if err = json.Unmarshal(data, &iceMsg); err != nil {
log.Printf("Failed to decode ICE candidate message from ingest for room: '%s' - reason: %s\n", room.Name, err)
return
}
@@ -165,7 +166,7 @@ func ingestHandler(room *Room) {
// SDP offer callback
room.WebSocket.RegisterMessageCallback("sdp", func(data []byte) {
var sdpMsg MessageSDP
if err = DecodeMessage(data, &sdpMsg); err != nil {
if err = json.Unmarshal(data, &sdpMsg); err != nil {
log.Printf("Failed to decode SDP message from ingest for room: '%s' - reason: %s\n", room.Name, err)
return
}
@@ -182,7 +183,7 @@ func ingestHandler(room *Room) {
// Log callback
room.WebSocket.RegisterMessageCallback("log", func(data []byte) {
var logMsg MessageLog
if err = DecodeMessage(data, &logMsg); err != nil {
if err = json.Unmarshal(data, &logMsg); err != nil {
log.Printf("Failed to decode log message from ingest for room: '%s' - reason: %s\n", room.Name, err)
return
}
@@ -192,7 +193,7 @@ func ingestHandler(room *Room) {
// Metrics callback
room.WebSocket.RegisterMessageCallback("metrics", func(data []byte) {
var metricsMsg MessageMetrics
if err = DecodeMessage(data, &metricsMsg); err != nil {
if err = json.Unmarshal(data, &metricsMsg); err != nil {
log.Printf("Failed to decode metrics message from ingest for room: '%s' - reason: %s\n", room.Name, err)
return
}

View File

@@ -2,20 +2,21 @@ package relay
import (
"fmt"
"google.golang.org/protobuf/types/known/timestamppb"
gen "relay/internal/proto"
"time"
)
type TimestampEntry struct {
Stage string `json:"stage"`
Time string `json:"time"` // ISO 8601 string
Stage string `json:"stage"`
Time time.Time `json:"time"`
}
// LatencyTracker provides a generic structure for measuring time taken at various stages in message processing.
// It can be embedded in message structs for tracking the flow of data and calculating round-trip latency.
type LatencyTracker struct {
SequenceID string `json:"sequence_id"`
Timestamps []TimestampEntry `json:"timestamps"`
Metadata map[string]string `json:"metadata,omitempty"`
SequenceID string `json:"sequence_id"`
Timestamps []TimestampEntry `json:"timestamps"`
}
// NewLatencyTracker initializes a new LatencyTracker with the given sequence ID
@@ -23,7 +24,6 @@ func NewLatencyTracker(sequenceID string) *LatencyTracker {
return &LatencyTracker{
SequenceID: sequenceID,
Timestamps: make([]TimestampEntry, 0),
Metadata: make(map[string]string),
}
}
@@ -32,7 +32,7 @@ func (lt *LatencyTracker) AddTimestamp(stage string) {
lt.Timestamps = append(lt.Timestamps, TimestampEntry{
Stage: stage,
// Ensure extremely precise UTC RFC3339 timestamps (down to nanoseconds)
Time: time.Now().UTC().Format(time.RFC3339Nano),
Time: time.Now().UTC(),
})
}
@@ -44,15 +44,11 @@ func (lt *LatencyTracker) TotalLatency() (int64, error) {
var earliest, latest time.Time
for _, ts := range lt.Timestamps {
t, err := time.Parse(time.RFC3339, ts.Time)
if err != nil {
return 0, err
if earliest.IsZero() || ts.Time.Before(earliest) {
earliest = ts.Time
}
if earliest.IsZero() || t.Before(earliest) {
earliest = t
}
if latest.IsZero() || t.After(latest) {
latest = t
if latest.IsZero() || ts.Time.After(latest) {
latest = ts.Time
}
}
@@ -67,14 +63,13 @@ func (lt *LatencyTracker) PainPoints(threshold time.Duration) []string {
for _, ts := range lt.Timestamps {
stage := ts.Stage
t := ts.Time
if lastStage == "" {
lastStage = stage
lastTime, _ = time.Parse(time.RFC3339, t)
lastTime = ts.Time
continue
}
currentTime, _ := time.Parse(time.RFC3339, t)
currentTime := ts.Time
if currentTime.Sub(lastTime) > threshold {
painPoints = append(painPoints, fmt.Sprintf("%s -> %s", lastStage, stage))
}
@@ -87,7 +82,7 @@ func (lt *LatencyTracker) PainPoints(threshold time.Duration) []string {
// StageLatency calculates the time taken between two specific stages.
func (lt *LatencyTracker) StageLatency(startStage, endStage string) (time.Duration, error) {
startTime, endTime := "", ""
var startTime, endTime time.Time
for _, ts := range lt.Timestamps {
if ts.Stage == startStage {
startTime = ts.Time
@@ -97,18 +92,41 @@ func (lt *LatencyTracker) StageLatency(startStage, endStage string) (time.Durati
}
}
if startTime == "" || endTime == "" {
/*if startTime == "" || endTime == "" {
return 0, fmt.Errorf("missing timestamps for stages: %s -> %s", startStage, endStage)
}
}*/
start, err := time.Parse(time.RFC3339, startTime)
if err != nil {
return 0, err
}
end, err := time.Parse(time.RFC3339, endTime)
if err != nil {
return 0, err
}
return end.Sub(start), nil
return endTime.Sub(startTime), nil
}
func LatencyTrackerFromProto(protolt *gen.ProtoLatencyTracker) *LatencyTracker {
ret := &LatencyTracker{
SequenceID: protolt.GetSequenceId(),
Timestamps: make([]TimestampEntry, 0),
}
for _, ts := range protolt.GetTimestamps() {
ret.Timestamps = append(ret.Timestamps, TimestampEntry{
Stage: ts.GetStage(),
Time: ts.GetTime().AsTime(),
})
}
return ret
}
func (lt *LatencyTracker) ToProto() *gen.ProtoLatencyTracker {
ret := &gen.ProtoLatencyTracker{
SequenceId: lt.SequenceID,
Timestamps: make([]*gen.ProtoTimestampEntry, len(lt.Timestamps)),
}
for i, timestamp := range lt.Timestamps {
ret.Timestamps[i] = &gen.ProtoTimestampEntry{
Stage: timestamp.Stage,
Time: timestamppb.New(timestamp.Time),
}
}
return ret
}

View File

@@ -1,27 +1,17 @@
package relay
import (
"bytes"
"compress/gzip"
"encoding/json"
"fmt"
"github.com/pion/webrtc/v4"
"time"
)
// OnMessageCallback is a callback for binary messages of given type
// OnMessageCallback is a callback for messages of given type
type OnMessageCallback func(data []byte)
// MessageBase is the base type for WS/DC messages.
type MessageBase struct {
PayloadType string `json:"payload_type"`
LatencyTracker LatencyTracker `json:"latency_tracker,omitempty"`
}
// MessageInput represents an input message.
type MessageInput struct {
MessageBase
Data string `json:"data"`
PayloadType string `json:"payload_type"`
Latency *LatencyTracker `json:"latency,omitempty"`
}
// MessageLog represents a log message.
@@ -93,50 +83,6 @@ type MessageAnswer struct {
AnswerType AnswerType `json:"answer_type"`
}
// EncodeMessage encodes a message to be sent with gzip compression
func EncodeMessage(msg interface{}) ([]byte, error) {
// Marshal the message to JSON
data, err := json.Marshal(msg)
if err != nil {
return nil, fmt.Errorf("failed to encode message: %w", err)
}
// Gzip compress the JSON
var compressedData bytes.Buffer
writer := gzip.NewWriter(&compressedData)
_, err = writer.Write(data)
if err != nil {
return nil, fmt.Errorf("failed to compress message: %w", err)
}
if err := writer.Close(); err != nil {
return nil, fmt.Errorf("failed to finalize compression: %w", err)
}
return compressedData.Bytes(), nil
}
// DecodeMessage decodes a message received with gzip decompression
func DecodeMessage(data []byte, target interface{}) error {
// Gzip decompress the data
reader, err := gzip.NewReader(bytes.NewReader(data))
if err != nil {
return fmt.Errorf("failed to initialize decompression: %w", err)
}
defer func(reader *gzip.Reader) {
if err = reader.Close(); err != nil {
fmt.Printf("failed to close reader: %v\n", err)
}
}(reader)
// Decode the JSON
err = json.NewDecoder(reader).Decode(target)
if err != nil {
return fmt.Errorf("failed to decode message: %w", err)
}
return nil
}
// SendLogMessageWS sends a log message to the given WebSocket connection.
func (ws *SafeWebSocket) SendLogMessageWS(level, message string) error {
msg := MessageLog{
@@ -145,12 +91,7 @@ func (ws *SafeWebSocket) SendLogMessageWS(level, message string) error {
Message: message,
Time: time.Now().Format(time.RFC3339),
}
encoded, err := EncodeMessage(msg)
if err != nil {
return fmt.Errorf("failed to encode log message: %w", err)
}
return ws.SendBinary(encoded)
return ws.SendJSON(msg)
}
// SendMetricsMessageWS sends a metrics message to the given WebSocket connection.
@@ -162,12 +103,7 @@ func (ws *SafeWebSocket) SendMetricsMessageWS(usageCPU, usageMemory float64, upt
Uptime: uptime,
PipelineLatency: pipelineLatency,
}
encoded, err := EncodeMessage(msg)
if err != nil {
return fmt.Errorf("failed to encode metrics message: %w", err)
}
return ws.SendBinary(encoded)
return ws.SendJSON(msg)
}
// SendICECandidateMessageWS sends an ICE candidate message to the given WebSocket connection.
@@ -176,12 +112,7 @@ func (ws *SafeWebSocket) SendICECandidateMessageWS(candidate webrtc.ICECandidate
MessageBase: MessageBase{PayloadType: "ice"},
Candidate: candidate,
}
encoded, err := EncodeMessage(msg)
if err != nil {
return fmt.Errorf("failed to encode ICE candidate message: %w", err)
}
return ws.SendBinary(encoded)
return ws.SendJSON(msg)
}
// SendSDPMessageWS sends an SDP message to the given WebSocket connection.
@@ -190,12 +121,7 @@ func (ws *SafeWebSocket) SendSDPMessageWS(sdp webrtc.SessionDescription) error {
MessageBase: MessageBase{PayloadType: "sdp"},
SDP: sdp,
}
encoded, err := EncodeMessage(msg)
if err != nil {
return fmt.Errorf("failed to encode SDP message: %w", err)
}
return ws.SendBinary(encoded)
return ws.SendJSON(msg)
}
// SendAnswerMessageWS sends an answer message to the given WebSocket connection.
@@ -204,24 +130,5 @@ func (ws *SafeWebSocket) SendAnswerMessageWS(answer AnswerType) error {
MessageBase: MessageBase{PayloadType: "answer"},
AnswerType: answer,
}
encoded, err := EncodeMessage(msg)
if err != nil {
return fmt.Errorf("failed to encode answer message: %w", err)
}
return ws.SendBinary(encoded)
}
// SendInputMessageDC sends an input message to the given DataChannel connection.
func (ndc *NestriDataChannel) SendInputMessageDC(data string) error {
msg := MessageInput{
MessageBase: MessageBase{PayloadType: "input"},
Data: data,
}
encoded, err := EncodeMessage(msg)
if err != nil {
return fmt.Errorf("failed to encode input message: %w", err)
}
return ndc.SendBinary(encoded)
return ws.SendJSON(msg)
}

View File

@@ -0,0 +1,203 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.4
// protoc (unknown)
// source: latency_tracker.proto
package proto
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type ProtoTimestampEntry struct {
state protoimpl.MessageState `protogen:"open.v1"`
Stage string `protobuf:"bytes,1,opt,name=stage,proto3" json:"stage,omitempty"`
Time *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=time,proto3" json:"time,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ProtoTimestampEntry) Reset() {
*x = ProtoTimestampEntry{}
mi := &file_latency_tracker_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ProtoTimestampEntry) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ProtoTimestampEntry) ProtoMessage() {}
func (x *ProtoTimestampEntry) ProtoReflect() protoreflect.Message {
mi := &file_latency_tracker_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ProtoTimestampEntry.ProtoReflect.Descriptor instead.
func (*ProtoTimestampEntry) Descriptor() ([]byte, []int) {
return file_latency_tracker_proto_rawDescGZIP(), []int{0}
}
func (x *ProtoTimestampEntry) GetStage() string {
if x != nil {
return x.Stage
}
return ""
}
func (x *ProtoTimestampEntry) GetTime() *timestamppb.Timestamp {
if x != nil {
return x.Time
}
return nil
}
type ProtoLatencyTracker struct {
state protoimpl.MessageState `protogen:"open.v1"`
SequenceId string `protobuf:"bytes,1,opt,name=sequence_id,json=sequenceId,proto3" json:"sequence_id,omitempty"`
Timestamps []*ProtoTimestampEntry `protobuf:"bytes,2,rep,name=timestamps,proto3" json:"timestamps,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ProtoLatencyTracker) Reset() {
*x = ProtoLatencyTracker{}
mi := &file_latency_tracker_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ProtoLatencyTracker) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ProtoLatencyTracker) ProtoMessage() {}
func (x *ProtoLatencyTracker) ProtoReflect() protoreflect.Message {
mi := &file_latency_tracker_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ProtoLatencyTracker.ProtoReflect.Descriptor instead.
func (*ProtoLatencyTracker) Descriptor() ([]byte, []int) {
return file_latency_tracker_proto_rawDescGZIP(), []int{1}
}
func (x *ProtoLatencyTracker) GetSequenceId() string {
if x != nil {
return x.SequenceId
}
return ""
}
func (x *ProtoLatencyTracker) GetTimestamps() []*ProtoTimestampEntry {
if x != nil {
return x.Timestamps
}
return nil
}
var File_latency_tracker_proto protoreflect.FileDescriptor
var file_latency_tracker_proto_rawDesc = string([]byte{
0x0a, 0x15, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x65,
0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f,
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f,
0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22,
0x5b, 0x0a, 0x13, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x12, 0x2e, 0x0a, 0x04,
0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f,
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d,
0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x22, 0x72, 0x0a, 0x13,
0x50, 0x72, 0x6f, 0x74, 0x6f, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x54, 0x72, 0x61, 0x63,
0x6b, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x5f,
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e,
0x63, 0x65, 0x49, 0x64, 0x12, 0x3a, 0x0a, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
0x70, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x45,
0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x73,
0x42, 0x16, 0x5a, 0x14, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
0x61, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
})
var (
file_latency_tracker_proto_rawDescOnce sync.Once
file_latency_tracker_proto_rawDescData []byte
)
func file_latency_tracker_proto_rawDescGZIP() []byte {
file_latency_tracker_proto_rawDescOnce.Do(func() {
file_latency_tracker_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_latency_tracker_proto_rawDesc), len(file_latency_tracker_proto_rawDesc)))
})
return file_latency_tracker_proto_rawDescData
}
var file_latency_tracker_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_latency_tracker_proto_goTypes = []any{
(*ProtoTimestampEntry)(nil), // 0: proto.ProtoTimestampEntry
(*ProtoLatencyTracker)(nil), // 1: proto.ProtoLatencyTracker
(*timestamppb.Timestamp)(nil), // 2: google.protobuf.Timestamp
}
var file_latency_tracker_proto_depIdxs = []int32{
2, // 0: proto.ProtoTimestampEntry.time:type_name -> google.protobuf.Timestamp
0, // 1: proto.ProtoLatencyTracker.timestamps:type_name -> proto.ProtoTimestampEntry
2, // [2:2] is the sub-list for method output_type
2, // [2:2] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
}
func init() { file_latency_tracker_proto_init() }
func file_latency_tracker_proto_init() {
if File_latency_tracker_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_latency_tracker_proto_rawDesc), len(file_latency_tracker_proto_rawDesc)),
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_latency_tracker_proto_goTypes,
DependencyIndexes: file_latency_tracker_proto_depIdxs,
MessageInfos: file_latency_tracker_proto_msgTypes,
}.Build()
File_latency_tracker_proto = out.File
file_latency_tracker_proto_goTypes = nil
file_latency_tracker_proto_depIdxs = nil
}

View File

@@ -0,0 +1,207 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.4
// protoc (unknown)
// source: messages.proto
package proto
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type ProtoMessageBase struct {
state protoimpl.MessageState `protogen:"open.v1"`
PayloadType string `protobuf:"bytes,1,opt,name=payload_type,json=payloadType,proto3" json:"payload_type,omitempty"`
Latency *ProtoLatencyTracker `protobuf:"bytes,2,opt,name=latency,proto3" json:"latency,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ProtoMessageBase) Reset() {
*x = ProtoMessageBase{}
mi := &file_messages_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ProtoMessageBase) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ProtoMessageBase) ProtoMessage() {}
func (x *ProtoMessageBase) ProtoReflect() protoreflect.Message {
mi := &file_messages_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ProtoMessageBase.ProtoReflect.Descriptor instead.
func (*ProtoMessageBase) Descriptor() ([]byte, []int) {
return file_messages_proto_rawDescGZIP(), []int{0}
}
func (x *ProtoMessageBase) GetPayloadType() string {
if x != nil {
return x.PayloadType
}
return ""
}
func (x *ProtoMessageBase) GetLatency() *ProtoLatencyTracker {
if x != nil {
return x.Latency
}
return nil
}
type ProtoMessageInput struct {
state protoimpl.MessageState `protogen:"open.v1"`
MessageBase *ProtoMessageBase `protobuf:"bytes,1,opt,name=message_base,json=messageBase,proto3" json:"message_base,omitempty"`
Data *ProtoInput `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ProtoMessageInput) Reset() {
*x = ProtoMessageInput{}
mi := &file_messages_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ProtoMessageInput) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ProtoMessageInput) ProtoMessage() {}
func (x *ProtoMessageInput) ProtoReflect() protoreflect.Message {
mi := &file_messages_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ProtoMessageInput.ProtoReflect.Descriptor instead.
func (*ProtoMessageInput) Descriptor() ([]byte, []int) {
return file_messages_proto_rawDescGZIP(), []int{1}
}
func (x *ProtoMessageInput) GetMessageBase() *ProtoMessageBase {
if x != nil {
return x.MessageBase
}
return nil
}
func (x *ProtoMessageInput) GetData() *ProtoInput {
if x != nil {
return x.Data
}
return nil
}
var File_messages_proto protoreflect.FileDescriptor
var file_messages_proto_rawDesc = string([]byte{
0x0a, 0x0e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x12, 0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0b, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x15, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x74, 0x72,
0x61, 0x63, 0x6b, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x6b, 0x0a, 0x10, 0x50,
0x72, 0x6f, 0x74, 0x6f, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x61, 0x73, 0x65, 0x12,
0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x54, 0x79,
0x70, 0x65, 0x12, 0x34, 0x0a, 0x07, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x72, 0x6f, 0x74,
0x6f, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x65, 0x72, 0x52,
0x07, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x22, 0x76, 0x0a, 0x11, 0x50, 0x72, 0x6f, 0x74,
0x6f, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x3a, 0x0a,
0x0c, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x72, 0x6f, 0x74,
0x6f, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x61, 0x73, 0x65, 0x52, 0x0b, 0x6d, 0x65,
0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x61, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x04, 0x64, 0x61, 0x74,
0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,
0x50, 0x72, 0x6f, 0x74, 0x6f, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61,
0x42, 0x16, 0x5a, 0x14, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
0x61, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
})
var (
file_messages_proto_rawDescOnce sync.Once
file_messages_proto_rawDescData []byte
)
func file_messages_proto_rawDescGZIP() []byte {
file_messages_proto_rawDescOnce.Do(func() {
file_messages_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_messages_proto_rawDesc), len(file_messages_proto_rawDesc)))
})
return file_messages_proto_rawDescData
}
var file_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_messages_proto_goTypes = []any{
(*ProtoMessageBase)(nil), // 0: proto.ProtoMessageBase
(*ProtoMessageInput)(nil), // 1: proto.ProtoMessageInput
(*ProtoLatencyTracker)(nil), // 2: proto.ProtoLatencyTracker
(*ProtoInput)(nil), // 3: proto.ProtoInput
}
var file_messages_proto_depIdxs = []int32{
2, // 0: proto.ProtoMessageBase.latency:type_name -> proto.ProtoLatencyTracker
0, // 1: proto.ProtoMessageInput.message_base:type_name -> proto.ProtoMessageBase
3, // 2: proto.ProtoMessageInput.data:type_name -> proto.ProtoInput
3, // [3:3] is the sub-list for method output_type
3, // [3:3] is the sub-list for method input_type
3, // [3:3] is the sub-list for extension type_name
3, // [3:3] is the sub-list for extension extendee
0, // [0:3] is the sub-list for field type_name
}
func init() { file_messages_proto_init() }
func file_messages_proto_init() {
if File_messages_proto != nil {
return
}
file_types_proto_init()
file_latency_tracker_proto_init()
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_messages_proto_rawDesc), len(file_messages_proto_rawDesc)),
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_messages_proto_goTypes,
DependencyIndexes: file_messages_proto_depIdxs,
MessageInfos: file_messages_proto_msgTypes,
}.Build()
File_messages_proto = out.File
file_messages_proto_goTypes = nil
file_messages_proto_depIdxs = nil
}

View File

@@ -0,0 +1,713 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.4
// protoc (unknown)
// source: types.proto
package proto
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// MouseMove message
type ProtoMouseMove struct {
state protoimpl.MessageState `protogen:"open.v1"`
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` // Fixed value "MouseMove"
X int32 `protobuf:"varint,2,opt,name=x,proto3" json:"x,omitempty"`
Y int32 `protobuf:"varint,3,opt,name=y,proto3" json:"y,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ProtoMouseMove) Reset() {
*x = ProtoMouseMove{}
mi := &file_types_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ProtoMouseMove) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ProtoMouseMove) ProtoMessage() {}
func (x *ProtoMouseMove) ProtoReflect() protoreflect.Message {
mi := &file_types_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ProtoMouseMove.ProtoReflect.Descriptor instead.
func (*ProtoMouseMove) Descriptor() ([]byte, []int) {
return file_types_proto_rawDescGZIP(), []int{0}
}
func (x *ProtoMouseMove) GetType() string {
if x != nil {
return x.Type
}
return ""
}
func (x *ProtoMouseMove) GetX() int32 {
if x != nil {
return x.X
}
return 0
}
func (x *ProtoMouseMove) GetY() int32 {
if x != nil {
return x.Y
}
return 0
}
// MouseMoveAbs message
type ProtoMouseMoveAbs struct {
state protoimpl.MessageState `protogen:"open.v1"`
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` // Fixed value "MouseMoveAbs"
X int32 `protobuf:"varint,2,opt,name=x,proto3" json:"x,omitempty"`
Y int32 `protobuf:"varint,3,opt,name=y,proto3" json:"y,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ProtoMouseMoveAbs) Reset() {
*x = ProtoMouseMoveAbs{}
mi := &file_types_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ProtoMouseMoveAbs) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ProtoMouseMoveAbs) ProtoMessage() {}
func (x *ProtoMouseMoveAbs) ProtoReflect() protoreflect.Message {
mi := &file_types_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ProtoMouseMoveAbs.ProtoReflect.Descriptor instead.
func (*ProtoMouseMoveAbs) Descriptor() ([]byte, []int) {
return file_types_proto_rawDescGZIP(), []int{1}
}
func (x *ProtoMouseMoveAbs) GetType() string {
if x != nil {
return x.Type
}
return ""
}
func (x *ProtoMouseMoveAbs) GetX() int32 {
if x != nil {
return x.X
}
return 0
}
func (x *ProtoMouseMoveAbs) GetY() int32 {
if x != nil {
return x.Y
}
return 0
}
// MouseWheel message
type ProtoMouseWheel struct {
state protoimpl.MessageState `protogen:"open.v1"`
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` // Fixed value "MouseWheel"
X int32 `protobuf:"varint,2,opt,name=x,proto3" json:"x,omitempty"`
Y int32 `protobuf:"varint,3,opt,name=y,proto3" json:"y,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ProtoMouseWheel) Reset() {
*x = ProtoMouseWheel{}
mi := &file_types_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ProtoMouseWheel) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ProtoMouseWheel) ProtoMessage() {}
func (x *ProtoMouseWheel) ProtoReflect() protoreflect.Message {
mi := &file_types_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ProtoMouseWheel.ProtoReflect.Descriptor instead.
func (*ProtoMouseWheel) Descriptor() ([]byte, []int) {
return file_types_proto_rawDescGZIP(), []int{2}
}
func (x *ProtoMouseWheel) GetType() string {
if x != nil {
return x.Type
}
return ""
}
func (x *ProtoMouseWheel) GetX() int32 {
if x != nil {
return x.X
}
return 0
}
func (x *ProtoMouseWheel) GetY() int32 {
if x != nil {
return x.Y
}
return 0
}
// MouseKeyDown message
type ProtoMouseKeyDown struct {
state protoimpl.MessageState `protogen:"open.v1"`
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` // Fixed value "MouseKeyDown"
Key int32 `protobuf:"varint,2,opt,name=key,proto3" json:"key,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ProtoMouseKeyDown) Reset() {
*x = ProtoMouseKeyDown{}
mi := &file_types_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ProtoMouseKeyDown) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ProtoMouseKeyDown) ProtoMessage() {}
func (x *ProtoMouseKeyDown) ProtoReflect() protoreflect.Message {
mi := &file_types_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ProtoMouseKeyDown.ProtoReflect.Descriptor instead.
func (*ProtoMouseKeyDown) Descriptor() ([]byte, []int) {
return file_types_proto_rawDescGZIP(), []int{3}
}
func (x *ProtoMouseKeyDown) GetType() string {
if x != nil {
return x.Type
}
return ""
}
func (x *ProtoMouseKeyDown) GetKey() int32 {
if x != nil {
return x.Key
}
return 0
}
// MouseKeyUp message
type ProtoMouseKeyUp struct {
state protoimpl.MessageState `protogen:"open.v1"`
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` // Fixed value "MouseKeyUp"
Key int32 `protobuf:"varint,2,opt,name=key,proto3" json:"key,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ProtoMouseKeyUp) Reset() {
*x = ProtoMouseKeyUp{}
mi := &file_types_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ProtoMouseKeyUp) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ProtoMouseKeyUp) ProtoMessage() {}
func (x *ProtoMouseKeyUp) ProtoReflect() protoreflect.Message {
mi := &file_types_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ProtoMouseKeyUp.ProtoReflect.Descriptor instead.
func (*ProtoMouseKeyUp) Descriptor() ([]byte, []int) {
return file_types_proto_rawDescGZIP(), []int{4}
}
func (x *ProtoMouseKeyUp) GetType() string {
if x != nil {
return x.Type
}
return ""
}
func (x *ProtoMouseKeyUp) GetKey() int32 {
if x != nil {
return x.Key
}
return 0
}
// KeyDown message
type ProtoKeyDown struct {
state protoimpl.MessageState `protogen:"open.v1"`
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` // Fixed value "KeyDown"
Key int32 `protobuf:"varint,2,opt,name=key,proto3" json:"key,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ProtoKeyDown) Reset() {
*x = ProtoKeyDown{}
mi := &file_types_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ProtoKeyDown) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ProtoKeyDown) ProtoMessage() {}
func (x *ProtoKeyDown) ProtoReflect() protoreflect.Message {
mi := &file_types_proto_msgTypes[5]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ProtoKeyDown.ProtoReflect.Descriptor instead.
func (*ProtoKeyDown) Descriptor() ([]byte, []int) {
return file_types_proto_rawDescGZIP(), []int{5}
}
func (x *ProtoKeyDown) GetType() string {
if x != nil {
return x.Type
}
return ""
}
func (x *ProtoKeyDown) GetKey() int32 {
if x != nil {
return x.Key
}
return 0
}
// KeyUp message
type ProtoKeyUp struct {
state protoimpl.MessageState `protogen:"open.v1"`
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` // Fixed value "KeyUp"
Key int32 `protobuf:"varint,2,opt,name=key,proto3" json:"key,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ProtoKeyUp) Reset() {
*x = ProtoKeyUp{}
mi := &file_types_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ProtoKeyUp) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ProtoKeyUp) ProtoMessage() {}
func (x *ProtoKeyUp) ProtoReflect() protoreflect.Message {
mi := &file_types_proto_msgTypes[6]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ProtoKeyUp.ProtoReflect.Descriptor instead.
func (*ProtoKeyUp) Descriptor() ([]byte, []int) {
return file_types_proto_rawDescGZIP(), []int{6}
}
func (x *ProtoKeyUp) GetType() string {
if x != nil {
return x.Type
}
return ""
}
func (x *ProtoKeyUp) GetKey() int32 {
if x != nil {
return x.Key
}
return 0
}
// Union of all Input types
type ProtoInput struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Types that are valid to be assigned to InputType:
//
// *ProtoInput_MouseMove
// *ProtoInput_MouseMoveAbs
// *ProtoInput_MouseWheel
// *ProtoInput_MouseKeyDown
// *ProtoInput_MouseKeyUp
// *ProtoInput_KeyDown
// *ProtoInput_KeyUp
InputType isProtoInput_InputType `protobuf_oneof:"input_type"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ProtoInput) Reset() {
*x = ProtoInput{}
mi := &file_types_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ProtoInput) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ProtoInput) ProtoMessage() {}
func (x *ProtoInput) ProtoReflect() protoreflect.Message {
mi := &file_types_proto_msgTypes[7]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ProtoInput.ProtoReflect.Descriptor instead.
func (*ProtoInput) Descriptor() ([]byte, []int) {
return file_types_proto_rawDescGZIP(), []int{7}
}
func (x *ProtoInput) GetInputType() isProtoInput_InputType {
if x != nil {
return x.InputType
}
return nil
}
func (x *ProtoInput) GetMouseMove() *ProtoMouseMove {
if x != nil {
if x, ok := x.InputType.(*ProtoInput_MouseMove); ok {
return x.MouseMove
}
}
return nil
}
func (x *ProtoInput) GetMouseMoveAbs() *ProtoMouseMoveAbs {
if x != nil {
if x, ok := x.InputType.(*ProtoInput_MouseMoveAbs); ok {
return x.MouseMoveAbs
}
}
return nil
}
func (x *ProtoInput) GetMouseWheel() *ProtoMouseWheel {
if x != nil {
if x, ok := x.InputType.(*ProtoInput_MouseWheel); ok {
return x.MouseWheel
}
}
return nil
}
func (x *ProtoInput) GetMouseKeyDown() *ProtoMouseKeyDown {
if x != nil {
if x, ok := x.InputType.(*ProtoInput_MouseKeyDown); ok {
return x.MouseKeyDown
}
}
return nil
}
func (x *ProtoInput) GetMouseKeyUp() *ProtoMouseKeyUp {
if x != nil {
if x, ok := x.InputType.(*ProtoInput_MouseKeyUp); ok {
return x.MouseKeyUp
}
}
return nil
}
func (x *ProtoInput) GetKeyDown() *ProtoKeyDown {
if x != nil {
if x, ok := x.InputType.(*ProtoInput_KeyDown); ok {
return x.KeyDown
}
}
return nil
}
func (x *ProtoInput) GetKeyUp() *ProtoKeyUp {
if x != nil {
if x, ok := x.InputType.(*ProtoInput_KeyUp); ok {
return x.KeyUp
}
}
return nil
}
type isProtoInput_InputType interface {
isProtoInput_InputType()
}
type ProtoInput_MouseMove struct {
MouseMove *ProtoMouseMove `protobuf:"bytes,1,opt,name=mouse_move,json=mouseMove,proto3,oneof"`
}
type ProtoInput_MouseMoveAbs struct {
MouseMoveAbs *ProtoMouseMoveAbs `protobuf:"bytes,2,opt,name=mouse_move_abs,json=mouseMoveAbs,proto3,oneof"`
}
type ProtoInput_MouseWheel struct {
MouseWheel *ProtoMouseWheel `protobuf:"bytes,3,opt,name=mouse_wheel,json=mouseWheel,proto3,oneof"`
}
type ProtoInput_MouseKeyDown struct {
MouseKeyDown *ProtoMouseKeyDown `protobuf:"bytes,4,opt,name=mouse_key_down,json=mouseKeyDown,proto3,oneof"`
}
type ProtoInput_MouseKeyUp struct {
MouseKeyUp *ProtoMouseKeyUp `protobuf:"bytes,5,opt,name=mouse_key_up,json=mouseKeyUp,proto3,oneof"`
}
type ProtoInput_KeyDown struct {
KeyDown *ProtoKeyDown `protobuf:"bytes,6,opt,name=key_down,json=keyDown,proto3,oneof"`
}
type ProtoInput_KeyUp struct {
KeyUp *ProtoKeyUp `protobuf:"bytes,7,opt,name=key_up,json=keyUp,proto3,oneof"`
}
func (*ProtoInput_MouseMove) isProtoInput_InputType() {}
func (*ProtoInput_MouseMoveAbs) isProtoInput_InputType() {}
func (*ProtoInput_MouseWheel) isProtoInput_InputType() {}
func (*ProtoInput_MouseKeyDown) isProtoInput_InputType() {}
func (*ProtoInput_MouseKeyUp) isProtoInput_InputType() {}
func (*ProtoInput_KeyDown) isProtoInput_InputType() {}
func (*ProtoInput_KeyUp) isProtoInput_InputType() {}
var File_types_proto protoreflect.FileDescriptor
var file_types_proto_rawDesc = string([]byte{
0x0a, 0x0b, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x22, 0x40, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x4d, 0x6f, 0x75,
0x73, 0x65, 0x4d, 0x6f, 0x76, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x0c, 0x0a, 0x01, 0x78, 0x18,
0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x01, 0x78, 0x12, 0x0c, 0x0a, 0x01, 0x79, 0x18, 0x03, 0x20,
0x01, 0x28, 0x05, 0x52, 0x01, 0x79, 0x22, 0x43, 0x0a, 0x11, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x4d,
0x6f, 0x75, 0x73, 0x65, 0x4d, 0x6f, 0x76, 0x65, 0x41, 0x62, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x74,
0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12,
0x0c, 0x0a, 0x01, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x01, 0x78, 0x12, 0x0c, 0x0a,
0x01, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x01, 0x79, 0x22, 0x41, 0x0a, 0x0f, 0x50,
0x72, 0x6f, 0x74, 0x6f, 0x4d, 0x6f, 0x75, 0x73, 0x65, 0x57, 0x68, 0x65, 0x65, 0x6c, 0x12, 0x12,
0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79,
0x70, 0x65, 0x12, 0x0c, 0x0a, 0x01, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x01, 0x78,
0x12, 0x0c, 0x0a, 0x01, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x01, 0x79, 0x22, 0x39,
0x0a, 0x11, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x4d, 0x6f, 0x75, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x44,
0x6f, 0x77, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02,
0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x37, 0x0a, 0x0f, 0x50, 0x72, 0x6f,
0x74, 0x6f, 0x4d, 0x6f, 0x75, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x55, 0x70, 0x12, 0x12, 0x0a, 0x04,
0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65,
0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6b,
0x65, 0x79, 0x22, 0x34, 0x0a, 0x0c, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x4b, 0x65, 0x79, 0x44, 0x6f,
0x77, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20,
0x01, 0x28, 0x05, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x32, 0x0a, 0x0a, 0x50, 0x72, 0x6f, 0x74,
0x6f, 0x4b, 0x65, 0x79, 0x55, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65,
0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0xab, 0x03, 0x0a,
0x0a, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x36, 0x0a, 0x0a, 0x6d,
0x6f, 0x75, 0x73, 0x65, 0x5f, 0x6d, 0x6f, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x4d, 0x6f, 0x75,
0x73, 0x65, 0x4d, 0x6f, 0x76, 0x65, 0x48, 0x00, 0x52, 0x09, 0x6d, 0x6f, 0x75, 0x73, 0x65, 0x4d,
0x6f, 0x76, 0x65, 0x12, 0x40, 0x0a, 0x0e, 0x6d, 0x6f, 0x75, 0x73, 0x65, 0x5f, 0x6d, 0x6f, 0x76,
0x65, 0x5f, 0x61, 0x62, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x4d, 0x6f, 0x75, 0x73, 0x65, 0x4d, 0x6f,
0x76, 0x65, 0x41, 0x62, 0x73, 0x48, 0x00, 0x52, 0x0c, 0x6d, 0x6f, 0x75, 0x73, 0x65, 0x4d, 0x6f,
0x76, 0x65, 0x41, 0x62, 0x73, 0x12, 0x39, 0x0a, 0x0b, 0x6d, 0x6f, 0x75, 0x73, 0x65, 0x5f, 0x77,
0x68, 0x65, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x4d, 0x6f, 0x75, 0x73, 0x65, 0x57, 0x68, 0x65,
0x65, 0x6c, 0x48, 0x00, 0x52, 0x0a, 0x6d, 0x6f, 0x75, 0x73, 0x65, 0x57, 0x68, 0x65, 0x65, 0x6c,
0x12, 0x40, 0x0a, 0x0e, 0x6d, 0x6f, 0x75, 0x73, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x64, 0x6f,
0x77, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x4d, 0x6f, 0x75, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x44, 0x6f,
0x77, 0x6e, 0x48, 0x00, 0x52, 0x0c, 0x6d, 0x6f, 0x75, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x44, 0x6f,
0x77, 0x6e, 0x12, 0x3a, 0x0a, 0x0c, 0x6d, 0x6f, 0x75, 0x73, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f,
0x75, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x4d, 0x6f, 0x75, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x55, 0x70,
0x48, 0x00, 0x52, 0x0a, 0x6d, 0x6f, 0x75, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x55, 0x70, 0x12, 0x30,
0x0a, 0x08, 0x6b, 0x65, 0x79, 0x5f, 0x64, 0x6f, 0x77, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x4b, 0x65,
0x79, 0x44, 0x6f, 0x77, 0x6e, 0x48, 0x00, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x44, 0x6f, 0x77, 0x6e,
0x12, 0x2a, 0x0a, 0x06, 0x6b, 0x65, 0x79, 0x5f, 0x75, 0x70, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x4b, 0x65,
0x79, 0x55, 0x70, 0x48, 0x00, 0x52, 0x05, 0x6b, 0x65, 0x79, 0x55, 0x70, 0x42, 0x0c, 0x0a, 0x0a,
0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x42, 0x16, 0x5a, 0x14, 0x72, 0x65,
0x6c, 0x61, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
})
var (
file_types_proto_rawDescOnce sync.Once
file_types_proto_rawDescData []byte
)
func file_types_proto_rawDescGZIP() []byte {
file_types_proto_rawDescOnce.Do(func() {
file_types_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_types_proto_rawDesc), len(file_types_proto_rawDesc)))
})
return file_types_proto_rawDescData
}
var file_types_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
var file_types_proto_goTypes = []any{
(*ProtoMouseMove)(nil), // 0: proto.ProtoMouseMove
(*ProtoMouseMoveAbs)(nil), // 1: proto.ProtoMouseMoveAbs
(*ProtoMouseWheel)(nil), // 2: proto.ProtoMouseWheel
(*ProtoMouseKeyDown)(nil), // 3: proto.ProtoMouseKeyDown
(*ProtoMouseKeyUp)(nil), // 4: proto.ProtoMouseKeyUp
(*ProtoKeyDown)(nil), // 5: proto.ProtoKeyDown
(*ProtoKeyUp)(nil), // 6: proto.ProtoKeyUp
(*ProtoInput)(nil), // 7: proto.ProtoInput
}
var file_types_proto_depIdxs = []int32{
0, // 0: proto.ProtoInput.mouse_move:type_name -> proto.ProtoMouseMove
1, // 1: proto.ProtoInput.mouse_move_abs:type_name -> proto.ProtoMouseMoveAbs
2, // 2: proto.ProtoInput.mouse_wheel:type_name -> proto.ProtoMouseWheel
3, // 3: proto.ProtoInput.mouse_key_down:type_name -> proto.ProtoMouseKeyDown
4, // 4: proto.ProtoInput.mouse_key_up:type_name -> proto.ProtoMouseKeyUp
5, // 5: proto.ProtoInput.key_down:type_name -> proto.ProtoKeyDown
6, // 6: proto.ProtoInput.key_up:type_name -> proto.ProtoKeyUp
7, // [7:7] is the sub-list for method output_type
7, // [7:7] is the sub-list for method input_type
7, // [7:7] is the sub-list for extension type_name
7, // [7:7] is the sub-list for extension extendee
0, // [0:7] is the sub-list for field type_name
}
func init() { file_types_proto_init() }
func file_types_proto_init() {
if File_types_proto != nil {
return
}
file_types_proto_msgTypes[7].OneofWrappers = []any{
(*ProtoInput_MouseMove)(nil),
(*ProtoInput_MouseMoveAbs)(nil),
(*ProtoInput_MouseWheel)(nil),
(*ProtoInput_MouseKeyDown)(nil),
(*ProtoInput_MouseKeyUp)(nil),
(*ProtoInput_KeyDown)(nil),
(*ProtoInput_KeyUp)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_types_proto_rawDesc), len(file_types_proto_rawDesc)),
NumEnums: 0,
NumMessages: 8,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_types_proto_goTypes,
DependencyIndexes: file_types_proto_depIdxs,
MessageInfos: file_types_proto_msgTypes,
}.Build()
File_types_proto = out.File
file_types_proto_goTypes = nil
file_types_proto_depIdxs = nil
}

View File

@@ -1,6 +1,7 @@
package relay
import (
"encoding/json"
"github.com/gorilla/websocket"
"log"
"sync"
@@ -10,22 +11,22 @@ import (
type SafeWebSocket struct {
*websocket.Conn
sync.Mutex
closeCallback func() // OnClose callback
binaryCallbacks map[string]OnMessageCallback // MessageBase type -> callback
closeCallback func() // OnClose callback
callbacks map[string]OnMessageCallback // MessageBase type -> callback
}
// NewSafeWebSocket creates a new SafeWebSocket from *websocket.Conn
func NewSafeWebSocket(conn *websocket.Conn) *SafeWebSocket {
ws := &SafeWebSocket{
Conn: conn,
closeCallback: nil,
binaryCallbacks: make(map[string]OnMessageCallback),
Conn: conn,
closeCallback: nil,
callbacks: make(map[string]OnMessageCallback),
}
// Launch a goroutine to handle binary messages
// Launch a goroutine to handle messages
go func() {
for {
// Read binary message
// Read message
kind, data, err := ws.Conn.ReadMessage()
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure, websocket.CloseNoStatusReceived) {
// If unexpected close error, break
@@ -42,22 +43,23 @@ func NewSafeWebSocket(conn *websocket.Conn) *SafeWebSocket {
switch kind {
case websocket.TextMessage:
// Ignore, we use binary messages
continue
case websocket.BinaryMessage:
// Decode message
var msg MessageBase
if err = DecodeMessage(data, &msg); err != nil {
log.Printf("Failed to decode binary WebSocket message, reason: %s\n", err)
if err = json.Unmarshal(data, &msg); err != nil {
log.Printf("Failed to decode text WebSocket message, reason: %s\n", err)
continue
}
// Handle message type callback
if callback, ok := ws.binaryCallbacks[msg.PayloadType]; ok {
if callback, ok := ws.callbacks[msg.PayloadType]; ok {
callback(data)
} // TODO: Log unknown message type?
break
case websocket.BinaryMessage:
break
default:
log.Printf("Unknown WebSocket message type: %d\n", kind)
break
}
}
@@ -88,18 +90,18 @@ func (ws *SafeWebSocket) SendBinary(data []byte) error {
func (ws *SafeWebSocket) RegisterMessageCallback(msgType string, callback OnMessageCallback) {
ws.Lock()
defer ws.Unlock()
if ws.binaryCallbacks == nil {
ws.binaryCallbacks = make(map[string]OnMessageCallback)
if ws.callbacks == nil {
ws.callbacks = make(map[string]OnMessageCallback)
}
ws.binaryCallbacks[msgType] = callback
ws.callbacks[msgType] = callback
}
// UnregisterMessageCallback removes the callback for binary message of given type
func (ws *SafeWebSocket) UnregisterMessageCallback(msgType string) {
ws.Lock()
defer ws.Unlock()
if ws.binaryCallbacks != nil {
delete(ws.binaryCallbacks, msgType)
if ws.callbacks != nil {
delete(ws.callbacks, msgType)
}
}
@@ -108,7 +110,7 @@ func (ws *SafeWebSocket) RegisterOnClose(callback func()) {
ws.closeCallback = func() {
// Clear our callbacks
ws.Lock()
ws.binaryCallbacks = nil
ws.callbacks = nil
ws.Unlock()
// Call the callback
callback()

View File

@@ -15,15 +15,15 @@ serde = {version = "1.0.214", features = ["derive"] }
tokio = { version = "1.41.0", features = ["full"] }
clap = { version = "4.5.20", features = ["env"] }
serde_json = "1.0.132"
webrtc = "0.11.0"
webrtc = "0.12.0"
regex = "1.11.1"
rand = "0.8.5"
rand = "0.9.0"
rustls = { version = "0.23.17", features = ["ring"] }
tokio-util = "0.7.12"
flate2 = "1.0.35"
tokio-tungstenite = { version = "0.24.0", features = ["native-tls"] }
tokio-tungstenite = { version = "0.26.1", features = ["native-tls"] }
log = { version = "0.4.22", features = ["std"] }
chrono = "0.4.38"
futures-util = "0.3.31"
num-derive = "0.4.2"
num-traits = "0.2.19"
prost = "0.13.4"
prost-types = "0.13.4"

View File

@@ -48,7 +48,10 @@ impl AppArgs {
.unwrap()
.parse::<u32>()
.unwrap_or(60),
relay_url: matches.get_one::<String>("relay-url").unwrap().clone(),
relay_url: matches
.get_one::<String>("relay-url")
.expect("relay url cannot be empty")
.clone(),
// Generate random room name if not provided
room: matches
.get_one::<String>("room")

View File

@@ -1,5 +1,5 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Serialize, Deserialize, Debug)]
pub struct TimestampEntry {

View File

@@ -5,6 +5,7 @@ mod latency;
mod messages;
mod nestrisink;
mod websocket;
mod proto;
use crate::args::encoding_args;
use crate::nestrisink::NestriSignaller;

View File

@@ -1,50 +1,14 @@
use std::error::Error;
use std::io::{Read, Write};
use flate2::Compression;
use flate2::read::GzDecoder;
use flate2::write::GzEncoder;
use crate::latency::LatencyTracker;
use num_derive::{FromPrimitive, ToPrimitive};
use num_traits::{FromPrimitive, ToPrimitive};
use serde::{Deserialize, Serialize};
use std::error::Error;
use webrtc::ice_transport::ice_candidate::RTCIceCandidateInit;
use webrtc::peer_connection::sdp::session_description::RTCSessionDescription;
use crate::latency::LatencyTracker;
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "type")]
pub enum InputMessage {
#[serde(rename = "mousemove")]
MouseMove { x: i32, y: i32 },
#[serde(rename = "mousemoveabs")]
MouseMoveAbs { x: i32, y: i32 },
#[serde(rename = "wheel")]
Wheel { x: f64, y: f64 },
#[serde(rename = "mousedown")]
MouseDown { key: i32 },
// Add other variants as needed
#[serde(rename = "mouseup")]
MouseUp { key: i32 },
#[serde(rename = "keydown")]
KeyDown { key: i32 },
#[serde(rename = "keyup")]
KeyUp { key: i32 },
}
#[derive(Serialize, Deserialize, Debug)]
pub struct MessageBase {
pub payload_type: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct MessageInput {
#[serde(flatten)]
pub base: MessageBase,
pub data: String,
pub latency: Option<LatencyTracker>,
}
@@ -136,34 +100,21 @@ pub struct MessageAnswer {
pub answer_type: AnswerType,
}
pub fn encode_message<T: Serialize>(message: &T) -> Result<Vec<u8>, Box<dyn Error>> {
pub fn encode_message<T: Serialize>(message: &T) -> Result<String, Box<dyn Error>> {
// Serialize the message to JSON
let json = serde_json::to_string(message)?;
// Compress the JSON using gzip
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
encoder.write_all(json.as_bytes())?;
let compressed_data = encoder.finish()?;
Ok(compressed_data)
Ok(json)
}
pub fn decode_message(data: &[u8]) -> Result<MessageBase, Box<dyn Error + Send + Sync>> {
let mut decoder = GzDecoder::new(data);
let mut decompressed_data = String::new();
decoder.read_to_string(&mut decompressed_data)?;
let base_message: MessageBase = serde_json::from_str(&decompressed_data)?;
pub fn decode_message(data: String) -> Result<MessageBase, Box<dyn Error + Send + Sync>> {
println!("Data: {}", data);
let base_message: MessageBase = serde_json::from_str(&data)?;
Ok(base_message)
}
pub fn decode_message_as<T: for<'de> Deserialize<'de>>(
data: Vec<u8>,
data: String,
) -> Result<T, Box<dyn Error + Send + Sync>> {
let mut decoder = GzDecoder::new(data.as_slice());
let mut decompressed_data = String::new();
decoder.read_to_string(&mut decompressed_data)?;
let message: T = serde_json::from_str(&decompressed_data)?;
let message: T = serde_json::from_str(&data)?;
Ok(message)
}
}

View File

@@ -1,13 +1,18 @@
use crate::messages::{
decode_message_as, encode_message, AnswerType, InputMessage, JoinerType, MessageAnswer,
MessageBase, MessageICE, MessageInput, MessageJoin, MessageSDP,
decode_message_as, encode_message, AnswerType, JoinerType, MessageAnswer, MessageBase,
MessageICE, MessageJoin, MessageSDP,
};
use crate::proto::proto::proto_input::InputType::{
KeyDown, KeyUp, MouseKeyDown, MouseKeyUp, MouseMove, MouseMoveAbs, MouseWheel,
};
use crate::proto::proto::{ProtoInput, ProtoMessageInput};
use crate::websocket::NestriWebSocket;
use glib::subclass::prelude::*;
use gst::glib;
use gst::prelude::*;
use gst_webrtc::{gst_sdp, WebRTCSDPType, WebRTCSessionDescription};
use gstrswebrtc::signaller::{Signallable, SignallableImpl};
use prost::Message;
use std::collections::HashSet;
use std::sync::{Arc, LazyLock};
use std::sync::{Mutex, RwLock};
@@ -200,6 +205,7 @@ impl SignallableImpl for Signaller {
let join_msg = MessageJoin {
base: MessageBase {
payload_type: "join".to_string(),
latency: None,
},
joiner_type: JoinerType::JoinerNode,
};
@@ -237,6 +243,7 @@ impl SignallableImpl for Signaller {
let join_msg = MessageJoin {
base: MessageBase {
payload_type: "join".to_string(),
latency: None,
},
joiner_type: JoinerType::JoinerNode,
};
@@ -265,6 +272,7 @@ impl SignallableImpl for Signaller {
let sdp_message = MessageSDP {
base: MessageBase {
payload_type: "sdp".to_string(),
latency: None,
},
sdp: RTCSessionDescription::offer(sdp.sdp().as_text().unwrap()).unwrap(),
};
@@ -301,6 +309,7 @@ impl SignallableImpl for Signaller {
let ice_message = MessageICE {
base: MessageBase {
payload_type: "ice".to_string(),
latency: None,
},
candidate: candidate_init,
};
@@ -354,11 +363,9 @@ fn setup_data_channel(data_channel: &gst_webrtc::WebRTCDataChannel, pipeline: &g
data_channel.connect_on_message_data(move |_data_channel, data| {
if let Some(data) = data {
match decode_message_as::<MessageInput>(data.to_vec()) {
match ProtoMessageInput::decode(data.to_vec().as_slice()) {
Ok(message_input) => {
// Deserialize the input message data
if let Ok(input_msg) = serde_json::from_str::<InputMessage>(&message_input.data)
{
if let Some(input_msg) = message_input.data {
// Process the input message and create an event
if let Some(event) =
handle_input_message(input_msg, &pressed_keys, &pressed_buttons)
@@ -379,88 +386,92 @@ fn setup_data_channel(data_channel: &gst_webrtc::WebRTCDataChannel, pipeline: &g
}
fn handle_input_message(
input_msg: InputMessage,
input_msg: ProtoInput,
pressed_keys: &Arc<Mutex<HashSet<i32>>>,
pressed_buttons: &Arc<Mutex<HashSet<i32>>>,
) -> Option<gst::Event> {
match input_msg {
InputMessage::MouseMove { x, y } => {
let structure = gst::Structure::builder("MouseMoveRelative")
.field("pointer_x", x as f64)
.field("pointer_y", y as f64)
.build();
if let Some(input_type) = input_msg.input_type {
match input_type {
MouseMove(data) => {
let structure = gst::Structure::builder("MouseMoveRelative")
.field("pointer_x", data.x as f64)
.field("pointer_y", data.y as f64)
.build();
Some(gst::event::CustomUpstream::new(structure))
}
InputMessage::MouseMoveAbs { x, y } => {
let structure = gst::Structure::builder("MouseMoveAbsolute")
.field("pointer_x", x as f64)
.field("pointer_y", y as f64)
.build();
Some(gst::event::CustomUpstream::new(structure))
}
InputMessage::KeyDown { key } => {
let mut keys = pressed_keys.lock().unwrap();
// If the key is already pressed, return to prevent key lockup
if keys.contains(&key) {
return None;
Some(gst::event::CustomUpstream::new(structure))
}
keys.insert(key);
MouseMoveAbs(data) => {
let structure = gst::Structure::builder("MouseMoveAbsolute")
.field("pointer_x", data.x as f64)
.field("pointer_y", data.y as f64)
.build();
let structure = gst::Structure::builder("KeyboardKey")
.field("key", key as u32)
.field("pressed", true)
.build();
Some(gst::event::CustomUpstream::new(structure))
}
InputMessage::KeyUp { key } => {
let mut keys = pressed_keys.lock().unwrap();
// Remove the key from the pressed state when released
keys.remove(&key);
let structure = gst::Structure::builder("KeyboardKey")
.field("key", key as u32)
.field("pressed", false)
.build();
Some(gst::event::CustomUpstream::new(structure))
}
InputMessage::Wheel { x, y } => {
let structure = gst::Structure::builder("MouseAxis")
.field("x", x as f64)
.field("y", y as f64)
.build();
Some(gst::event::CustomUpstream::new(structure))
}
InputMessage::MouseDown { key } => {
let mut buttons = pressed_buttons.lock().unwrap();
// If the button is already pressed, return to prevent button lockup
if buttons.contains(&key) {
return None;
Some(gst::event::CustomUpstream::new(structure))
}
buttons.insert(key);
KeyDown(data) => {
let mut keys = pressed_keys.lock().unwrap();
// If the key is already pressed, return to prevent key lockup
if keys.contains(&data.key) {
return None;
}
keys.insert(data.key);
let structure = gst::Structure::builder("MouseButton")
.field("button", key as u32)
.field("pressed", true)
.build();
let structure = gst::Structure::builder("KeyboardKey")
.field("key", data.key as u32)
.field("pressed", true)
.build();
Some(gst::event::CustomUpstream::new(structure))
}
InputMessage::MouseUp { key } => {
let mut buttons = pressed_buttons.lock().unwrap();
// Remove the button from the pressed state when released
buttons.remove(&key);
let structure = gst::Structure::builder("MouseButton")
.field("button", key as u32)
.field("pressed", false)
.build();
Some(gst::event::CustomUpstream::new(structure))
Some(gst::event::CustomUpstream::new(structure))
}
KeyUp(data) => {
let mut keys = pressed_keys.lock().unwrap();
// Remove the key from the pressed state when released
keys.remove(&data.key);
let structure = gst::Structure::builder("KeyboardKey")
.field("key", data.key as u32)
.field("pressed", false)
.build();
Some(gst::event::CustomUpstream::new(structure))
}
MouseWheel(data) => {
let structure = gst::Structure::builder("MouseAxis")
.field("x", data.x as f64)
.field("y", data.y as f64)
.build();
Some(gst::event::CustomUpstream::new(structure))
}
MouseKeyDown(data) => {
let mut buttons = pressed_buttons.lock().unwrap();
// If the button is already pressed, return to prevent button lockup
if buttons.contains(&data.key) {
return None;
}
buttons.insert(data.key);
let structure = gst::Structure::builder("MouseButton")
.field("button", data.key as u32)
.field("pressed", true)
.build();
Some(gst::event::CustomUpstream::new(structure))
}
MouseKeyUp(data) => {
let mut buttons = pressed_buttons.lock().unwrap();
// Remove the button from the pressed state when released
buttons.remove(&data.key);
let structure = gst::Structure::builder("MouseButton")
.field("button", data.key as u32)
.field("pressed", false)
.build();
Some(gst::event::CustomUpstream::new(structure))
}
}
} else {
None
}
}

View File

@@ -0,0 +1 @@
pub mod proto;

View File

@@ -0,0 +1,139 @@
// @generated
// This file is @generated by prost-build.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtoTimestampEntry {
#[prost(string, tag="1")]
pub stage: ::prost::alloc::string::String,
#[prost(message, optional, tag="2")]
pub time: ::core::option::Option<::prost_types::Timestamp>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtoLatencyTracker {
#[prost(string, tag="1")]
pub sequence_id: ::prost::alloc::string::String,
#[prost(message, repeated, tag="2")]
pub timestamps: ::prost::alloc::vec::Vec<ProtoTimestampEntry>,
}
/// MouseMove message
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtoMouseMove {
/// Fixed value "MouseMove"
#[prost(string, tag="1")]
pub r#type: ::prost::alloc::string::String,
#[prost(int32, tag="2")]
pub x: i32,
#[prost(int32, tag="3")]
pub y: i32,
}
/// MouseMoveAbs message
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtoMouseMoveAbs {
/// Fixed value "MouseMoveAbs"
#[prost(string, tag="1")]
pub r#type: ::prost::alloc::string::String,
#[prost(int32, tag="2")]
pub x: i32,
#[prost(int32, tag="3")]
pub y: i32,
}
/// MouseWheel message
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtoMouseWheel {
/// Fixed value "MouseWheel"
#[prost(string, tag="1")]
pub r#type: ::prost::alloc::string::String,
#[prost(int32, tag="2")]
pub x: i32,
#[prost(int32, tag="3")]
pub y: i32,
}
/// MouseKeyDown message
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtoMouseKeyDown {
/// Fixed value "MouseKeyDown"
#[prost(string, tag="1")]
pub r#type: ::prost::alloc::string::String,
#[prost(int32, tag="2")]
pub key: i32,
}
/// MouseKeyUp message
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtoMouseKeyUp {
/// Fixed value "MouseKeyUp"
#[prost(string, tag="1")]
pub r#type: ::prost::alloc::string::String,
#[prost(int32, tag="2")]
pub key: i32,
}
/// KeyDown message
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtoKeyDown {
/// Fixed value "KeyDown"
#[prost(string, tag="1")]
pub r#type: ::prost::alloc::string::String,
#[prost(int32, tag="2")]
pub key: i32,
}
/// KeyUp message
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtoKeyUp {
/// Fixed value "KeyUp"
#[prost(string, tag="1")]
pub r#type: ::prost::alloc::string::String,
#[prost(int32, tag="2")]
pub key: i32,
}
/// Union of all Input types
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtoInput {
#[prost(oneof="proto_input::InputType", tags="1, 2, 3, 4, 5, 6, 7")]
pub input_type: ::core::option::Option<proto_input::InputType>,
}
/// Nested message and enum types in `ProtoInput`.
pub mod proto_input {
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Oneof)]
pub enum InputType {
#[prost(message, tag="1")]
MouseMove(super::ProtoMouseMove),
#[prost(message, tag="2")]
MouseMoveAbs(super::ProtoMouseMoveAbs),
#[prost(message, tag="3")]
MouseWheel(super::ProtoMouseWheel),
#[prost(message, tag="4")]
MouseKeyDown(super::ProtoMouseKeyDown),
#[prost(message, tag="5")]
MouseKeyUp(super::ProtoMouseKeyUp),
#[prost(message, tag="6")]
KeyDown(super::ProtoKeyDown),
#[prost(message, tag="7")]
KeyUp(super::ProtoKeyUp),
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtoMessageBase {
#[prost(string, tag="1")]
pub payload_type: ::prost::alloc::string::String,
#[prost(message, optional, tag="2")]
pub latency: ::core::option::Option<ProtoLatencyTracker>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtoMessageInput {
#[prost(message, optional, tag="1")]
pub message_base: ::core::option::Option<ProtoMessageBase>,
#[prost(message, optional, tag="2")]
pub data: ::core::option::Option<ProtoInput>,
}
// @@protoc_insertion_point(module)

View File

@@ -10,10 +10,10 @@ use std::time::Duration;
use tokio::net::TcpStream;
use tokio::sync::{mpsc, Mutex, Notify};
use tokio::time::sleep;
use tokio_tungstenite::tungstenite::Message;
use tokio_tungstenite::tungstenite::{Message, Utf8Bytes};
use tokio_tungstenite::{connect_async, MaybeTlsStream, WebSocketStream};
type Callback = Box<dyn Fn(Vec<u8>) + Send + Sync>;
type Callback = Box<dyn Fn(String) + Send + Sync>;
type WSRead = SplitStream<WebSocketStream<MaybeTlsStream<TcpStream>>>;
type WSWrite = SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>;
@@ -23,7 +23,7 @@ pub struct NestriWebSocket {
reader: Arc<Mutex<Option<WSRead>>>,
writer: Arc<Mutex<Option<WSWrite>>>,
callbacks: Arc<RwLock<HashMap<String, Callback>>>,
message_tx: mpsc::UnboundedSender<Vec<u8>>,
message_tx: mpsc::UnboundedSender<String>,
reconnected_notify: Arc<Notify>,
}
impl NestriWebSocket {
@@ -95,8 +95,8 @@ impl NestriWebSocket {
while let Some(message_result) = ws_read.next().await {
match message_result {
Ok(message) => {
let data = message.into_data();
let base_message = match decode_message(&data) {
let data = message.into_text().expect("failed to turn message into text");
let base_message = match decode_message(data.to_string()) {
Ok(base_message) => base_message,
Err(e) => {
eprintln!("Failed to decode message: {:?}", e);
@@ -107,11 +107,14 @@ impl NestriWebSocket {
let callbacks_lock = callbacks.read().unwrap();
if let Some(callback) = callbacks_lock.get(&base_message.payload_type) {
let data = data.clone();
callback(data);
callback(data.to_string());
}
}
Err(e) => {
eprintln!("Error receiving message: {:?}, reconnecting in 3 seconds...", e);
eprintln!(
"Error receiving message: {:?}, reconnecting in 3 seconds...",
e
);
sleep(Duration::from_secs(3)).await;
self_clone.reconnect().await.unwrap();
break; // Break the inner loop to get a new ws_read
@@ -123,7 +126,7 @@ impl NestriWebSocket {
});
}
fn spawn_write_loop(&self, mut message_rx: mpsc::UnboundedReceiver<Vec<u8>>) {
fn spawn_write_loop(&self, mut message_rx: mpsc::UnboundedReceiver<String>) {
let writer = self.writer.clone();
let self_clone = self.clone();
@@ -136,7 +139,10 @@ impl NestriWebSocket {
let mut writer_lock = writer.lock().await;
if let Some(writer) = writer_lock.as_mut() {
// Try to send the message over the WebSocket
match writer.send(Message::Binary(message.clone())).await {
match writer
.send(Message::Text(Utf8Bytes::from(message.clone())))
.await
{
Ok(_) => {
// Message sent successfully
break;
@@ -196,7 +202,7 @@ impl NestriWebSocket {
}
/// Send a message through the WebSocket
pub fn send_message(&self, message: Vec<u8>) -> Result<(), Box<dyn Error>> {
pub fn send_message(&self, message: String) -> Result<(), Box<dyn Error>> {
self.message_tx
.send(message)
.map_err(|e| format!("Failed to send message: {:?}", e).into())
@@ -205,7 +211,7 @@ impl NestriWebSocket {
/// Register a callback for a specific response type
pub fn register_callback<F>(&self, response_type: &str, callback: F)
where
F: Fn(Vec<u8>) + Send + Sync + 'static,
F: Fn(String) + Send + Sync + 'static,
{
let mut callbacks_lock = self.callbacks.write().unwrap();
callbacks_lock.insert(response_type.to_string(), Box::new(callback));
@@ -234,6 +240,7 @@ impl Log for NestriWebSocket {
let log_message = MessageLog {
base: MessageBase {
payload_type: "log".to_string(),
latency: None,
},
level,
message,

7
protobufs/buf.yaml Normal file
View File

@@ -0,0 +1,7 @@
version: v2
breaking:
use:
- FILE
lint:
use:
- STANDARD

View File

@@ -0,0 +1,17 @@
syntax = "proto3";
import "google/protobuf/timestamp.proto";
option go_package = "relay/internal/proto";
package proto;
message ProtoTimestampEntry {
string stage = 1;
google.protobuf.Timestamp time = 2;
};
message ProtoLatencyTracker {
string sequence_id = 1;
repeated ProtoTimestampEntry timestamps = 2;
}

18
protobufs/messages.proto Normal file
View File

@@ -0,0 +1,18 @@
syntax = "proto3";
option go_package = "relay/internal/proto";
import "types.proto";
import "latency_tracker.proto";
package proto;
message ProtoMessageBase {
string payload_type = 1;
ProtoLatencyTracker latency = 2;
}
message ProtoMessageInput {
ProtoMessageBase message_base = 1;
ProtoInput data = 2;
}

63
protobufs/types.proto Normal file
View File

@@ -0,0 +1,63 @@
syntax = "proto3";
option go_package = "relay/internal/proto";
package proto;
// MouseMove message
message ProtoMouseMove {
string type = 1; // Fixed value "MouseMove"
int32 x = 2;
int32 y = 3;
}
// MouseMoveAbs message
message ProtoMouseMoveAbs {
string type = 1; // Fixed value "MouseMoveAbs"
int32 x = 2;
int32 y = 3;
}
// MouseWheel message
message ProtoMouseWheel {
string type = 1; // Fixed value "MouseWheel"
int32 x = 2;
int32 y = 3;
}
// MouseKeyDown message
message ProtoMouseKeyDown {
string type = 1; // Fixed value "MouseKeyDown"
int32 key = 2;
}
// MouseKeyUp message
message ProtoMouseKeyUp {
string type = 1; // Fixed value "MouseKeyUp"
int32 key = 2;
}
// KeyDown message
message ProtoKeyDown {
string type = 1; // Fixed value "KeyDown"
int32 key = 2;
}
// KeyUp message
message ProtoKeyUp {
string type = 1; // Fixed value "KeyUp"
int32 key = 2;
}
// Union of all Input types
message ProtoInput {
oneof input_type {
ProtoMouseMove mouse_move = 1;
ProtoMouseMoveAbs mouse_move_abs = 2;
ProtoMouseWheel mouse_wheel = 3;
ProtoMouseKeyDown mouse_key_down = 4;
ProtoMouseKeyUp mouse_key_up = 5;
ProtoKeyDown key_down = 6;
ProtoKeyUp key_up = 7;
}
}