mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-12 16:55:37 +02:00
✨ feat: Host a relay on Hetzner (#114)
We are hosting a [MoQ](https://quic.video) relay on a remote (bare metal) server on Hetzner With a lot of help from @victorpahuus
This commit is contained in:
197
packages/moq/transport/subscriber.ts
Normal file
197
packages/moq/transport/subscriber.ts
Normal file
@@ -0,0 +1,197 @@
|
||||
import * as Control from "./control"
|
||||
import { Queue, Watch } from "../common/async"
|
||||
import { Objects } from "./objects"
|
||||
import type { TrackReader, GroupReader, ObjectReader } from "./objects"
|
||||
|
||||
export class Subscriber {
|
||||
// Use to send control messages.
|
||||
#control: Control.Stream
|
||||
|
||||
// Use to send objects.
|
||||
#objects: Objects
|
||||
|
||||
// Announced broadcasts.
|
||||
#announce = new Map<string, AnnounceRecv>()
|
||||
#announceQueue = new Watch<AnnounceRecv[]>([])
|
||||
|
||||
// Our subscribed tracks.
|
||||
#subscribe = new Map<bigint, SubscribeSend>()
|
||||
#subscribeNext = 0n
|
||||
|
||||
constructor(control: Control.Stream, objects: Objects) {
|
||||
this.#control = control
|
||||
this.#objects = objects
|
||||
}
|
||||
|
||||
announced(): Watch<AnnounceRecv[]> {
|
||||
return this.#announceQueue
|
||||
}
|
||||
|
||||
async recv(msg: Control.Publisher) {
|
||||
if (msg.kind == Control.Msg.Announce) {
|
||||
await this.recvAnnounce(msg)
|
||||
} else if (msg.kind == Control.Msg.Unannounce) {
|
||||
this.recvUnannounce(msg)
|
||||
} else if (msg.kind == Control.Msg.SubscribeOk) {
|
||||
this.recvSubscribeOk(msg)
|
||||
} else if (msg.kind == Control.Msg.SubscribeError) {
|
||||
await this.recvSubscribeError(msg)
|
||||
} else if (msg.kind == Control.Msg.SubscribeDone) {
|
||||
await this.recvSubscribeDone(msg)
|
||||
} else {
|
||||
throw new Error(`unknown control message`) // impossible
|
||||
}
|
||||
}
|
||||
|
||||
async recvAnnounce(msg: Control.Announce) {
|
||||
if (this.#announce.has(msg.namespace)) {
|
||||
throw new Error(`duplicate announce for namespace: ${msg.namespace}`)
|
||||
}
|
||||
|
||||
await this.#control.send({ kind: Control.Msg.AnnounceOk, namespace: msg.namespace })
|
||||
|
||||
const announce = new AnnounceRecv(this.#control, msg.namespace)
|
||||
this.#announce.set(msg.namespace, announce)
|
||||
|
||||
this.#announceQueue.update((queue) => [...queue, announce])
|
||||
}
|
||||
|
||||
recvUnannounce(_msg: Control.Unannounce) {
|
||||
throw new Error(`TODO Unannounce`)
|
||||
}
|
||||
|
||||
async subscribe(namespace: string, track: string) {
|
||||
const id = this.#subscribeNext++
|
||||
|
||||
const subscribe = new SubscribeSend(this.#control, id, namespace, track)
|
||||
this.#subscribe.set(id, subscribe)
|
||||
|
||||
await this.#control.send({
|
||||
kind: Control.Msg.Subscribe,
|
||||
id,
|
||||
trackId: id,
|
||||
namespace,
|
||||
name: track,
|
||||
location: {
|
||||
mode: "latest_group",
|
||||
},
|
||||
})
|
||||
|
||||
return subscribe
|
||||
}
|
||||
|
||||
recvSubscribeOk(msg: Control.SubscribeOk) {
|
||||
const subscribe = this.#subscribe.get(msg.id)
|
||||
if (!subscribe) {
|
||||
throw new Error(`subscribe ok for unknown id: ${msg.id}`)
|
||||
}
|
||||
|
||||
subscribe.onOk()
|
||||
}
|
||||
|
||||
async recvSubscribeError(msg: Control.SubscribeError) {
|
||||
const subscribe = this.#subscribe.get(msg.id)
|
||||
if (!subscribe) {
|
||||
throw new Error(`subscribe error for unknown id: ${msg.id}`)
|
||||
}
|
||||
|
||||
await subscribe.onError(msg.code, msg.reason)
|
||||
}
|
||||
|
||||
async recvSubscribeDone(msg: Control.SubscribeDone) {
|
||||
const subscribe = this.#subscribe.get(msg.id)
|
||||
if (!subscribe) {
|
||||
throw new Error(`subscribe error for unknown id: ${msg.id}`)
|
||||
}
|
||||
|
||||
await subscribe.onError(msg.code, msg.reason)
|
||||
}
|
||||
|
||||
async recvObject(reader: TrackReader | GroupReader | ObjectReader) {
|
||||
const subscribe = this.#subscribe.get(reader.header.track)
|
||||
if (!subscribe) {
|
||||
throw new Error(`data for for unknown track: ${reader.header.track}`)
|
||||
}
|
||||
|
||||
await subscribe.onData(reader)
|
||||
}
|
||||
}
|
||||
|
||||
export class AnnounceRecv {
|
||||
#control: Control.Stream
|
||||
|
||||
readonly namespace: string
|
||||
|
||||
// The current state of the announce
|
||||
#state: "init" | "ack" | "closed" = "init"
|
||||
|
||||
constructor(control: Control.Stream, namespace: string) {
|
||||
this.#control = control // so we can send messages
|
||||
this.namespace = namespace
|
||||
}
|
||||
|
||||
// Acknowledge the subscription as valid.
|
||||
async ok() {
|
||||
if (this.#state !== "init") return
|
||||
this.#state = "ack"
|
||||
|
||||
// Send the control message.
|
||||
return this.#control.send({ kind: Control.Msg.AnnounceOk, namespace: this.namespace })
|
||||
}
|
||||
|
||||
async close(code = 0n, reason = "") {
|
||||
if (this.#state === "closed") return
|
||||
this.#state = "closed"
|
||||
|
||||
return this.#control.send({ kind: Control.Msg.AnnounceError, namespace: this.namespace, code, reason })
|
||||
}
|
||||
}
|
||||
|
||||
export class SubscribeSend {
|
||||
#control: Control.Stream
|
||||
#id: bigint
|
||||
|
||||
readonly namespace: string
|
||||
readonly track: string
|
||||
|
||||
// A queue of received streams for this subscription.
|
||||
#data = new Queue<TrackReader | GroupReader | ObjectReader>()
|
||||
|
||||
constructor(control: Control.Stream, id: bigint, namespace: string, track: string) {
|
||||
this.#control = control // so we can send messages
|
||||
this.#id = id
|
||||
this.namespace = namespace
|
||||
this.track = track
|
||||
}
|
||||
|
||||
async close(_code = 0n, _reason = "") {
|
||||
// TODO implement unsubscribe
|
||||
// await this.#inner.sendReset(code, reason)
|
||||
}
|
||||
|
||||
onOk() {
|
||||
// noop
|
||||
}
|
||||
|
||||
async onError(code: bigint, reason: string) {
|
||||
if (code == 0n) {
|
||||
return await this.#data.close()
|
||||
}
|
||||
|
||||
if (reason !== "") {
|
||||
reason = `: ${reason}`
|
||||
}
|
||||
|
||||
const err = new Error(`SUBSCRIBE_ERROR (${code})${reason}`)
|
||||
return await this.#data.abort(err)
|
||||
}
|
||||
|
||||
async onData(reader: TrackReader | GroupReader | ObjectReader) {
|
||||
if (!this.#data.closed()) await this.#data.push(reader)
|
||||
}
|
||||
|
||||
// Receive the next a readable data stream
|
||||
async data() {
|
||||
return await this.#data.next()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user