UNPKG

@koush/ring-client-api

Version:

Unofficial API for Ring doorbells, cameras, security alarm system and smart lighting

132 lines (115 loc) 3.38 kB
import { logDebug, logError } from './util' import { RtpSplitter, SrtpOptions } from '@homebridge/camera-utils' import { DingKind } from './ring-types' const stun = require('stun') const stunMagicCookie = 0x2112a442 // https://tools.ietf.org/html/rfc5389#section-6 export interface RtpStreamOptions extends SrtpOptions { port: number rtcpPort: number } export interface RtpOptions { audio: RtpStreamOptions video: RtpStreamOptions } export interface RtpStreamDescription extends RtpStreamOptions { ssrc?: number iceUFrag?: string icePwd?: string } export interface RtpDescription { address: string audio: RtpStreamDescription video: RtpStreamDescription } export function isStunMessage(message: Buffer) { return message.length > 8 && message.readInt32BE(4) === stunMagicCookie } export function sendStunBindingRequest({ rtpDescription, rtpSplitter, rtcpSplitter, localUfrag, type, }: { rtpSplitter: RtpSplitter rtcpSplitter: RtpSplitter rtpDescription: RtpDescription localUfrag?: string type: 'video' | 'audio' }) { const message = stun.createMessage(1), remoteDescription = rtpDescription[type], { address } = rtpDescription, { iceUFrag, icePwd, port, rtcpPort } = remoteDescription if (iceUFrag && icePwd && localUfrag) { // Full ICE supported. Send as formal stun request message.addUsername(iceUFrag + ':' + localUfrag) message.addMessageIntegrity(icePwd) stun .request(`${address}:${port}`, { socket: rtpSplitter.socket, message, }) .then(() => logDebug(`${type} stun complete`)) .catch((e: Error) => { logError(`${type} stun error`) logError(e) }) } else { // ICE not supported. Fire and forget the stun request for RTP and RTCP const encodedMessage = stun.encode(message) rtpSplitter.send(encodedMessage, { address, port }).catch(logError) rtcpSplitter .send(encodedMessage, { address, port: rtcpPort }) .catch(logError) } } export function createStunResponder(rtpSplitter: RtpSplitter) { return rtpSplitter.addMessageHandler(({ message, info }) => { if (!isStunMessage(message)) { return null } try { const decodedMessage = stun.decode(message), response = stun.createMessage( stun.constants.STUN_BINDING_RESPONSE, decodedMessage.transactionId ) response.addXorAddress(info.address, info.port) rtpSplitter.send(stun.encode(response), info).catch(logError) } catch (e) { logDebug('Failed to Decode STUN Message') logDebug(message.toString('hex')) logDebug(e) } return null }) } export interface LiveCallSession { alexa_port: 18443 app_session_token: string availability_zone: 'availability-zone' custom_timer: { max_sec: number } ding_id: string ding_kind: DingKind doorbot_id: number exp: number ip: string port: number private_ip: string rms_fqdn: string rms_version: string rsp_port: number rtsp_port: number session_id: string sip_port: number webrtc_port: number webrtc_url: string wwr_port: number } export function parseLiveCallSession(sessionId: string) { const encodedSession = sessionId.split('.')[1], buff = new Buffer(encodedSession, 'base64'), text = buff.toString('ascii') return JSON.parse(text) as LiveCallSession }