From 72df16d803bb3036d8673dadebb72311b288511d Mon Sep 17 00:00:00 2001 From: balzack Date: Wed, 22 Jan 2025 22:36:59 -0800 Subject: [PATCH] preparing webrtc connection --- app/sdk/src/api.ts | 27 +++++++++---- app/sdk/src/contact.ts | 13 ++++++ app/sdk/src/ring.ts | 92 +++++++++++++++++++++++++++++++++++------- app/sdk/src/session.ts | 2 +- 4 files changed, 110 insertions(+), 24 deletions(-) diff --git a/app/sdk/src/api.ts b/app/sdk/src/api.ts index 705b7969..c319664a 100644 --- a/app/sdk/src/api.ts +++ b/app/sdk/src/api.ts @@ -16,16 +16,25 @@ export interface Session { removeStatusListener(ev: (status: string) => void): void; } +export interface Link { + setStatusListener(ev: (status: string) => void): void; + clearStatusListener(): void; + setMessageListener(ev: (message: any) => void): void; + clearMessageListener(): void; + + getIce(): { urls: string; username: string; credential: string }[]; + sendMessage(message: any): Promise; + + close(): Promise; +} + export interface Ring { - addCallingListener(ev: (calls: Call[]) => void): void; - removeCallingListener(ev: (calls: Call[]) => void): void; + addRingingListener(ev: (calls: Call[]) => void): void; + removeRingingListener(ev: (calls: Call[]) => void): void; - addCallListener(ev: (call: Call | null) => void): void; - removeCallListener(ev: (call: Call | null) => void): void; - - accept(callId: string): void; - ignore(callId: string): void; - decline(callId: string): void; + accept(cardId: string, callId: string, contactNode: string): Promise; + decline(cardId: string, callId: string, contactNode: string): Promise; + ignore(cardId: string, callId: string): Promise; } export interface Settings { @@ -75,6 +84,8 @@ export interface Contact { setBlockedCard(cardId: string, blocked: boolean): Promise; getRegistry(handle: string | null, server: string | null): Promise; + callCard(cardId: string, ringInterval: number): Promise; + addCardListener(ev: (cards: Card[]) => void): void; removeCardListener(ev: (cards: Card[]) => void): void; } diff --git a/app/sdk/src/contact.ts b/app/sdk/src/contact.ts index a1c7990d..7c7ed92a 100644 --- a/app/sdk/src/contact.ts +++ b/app/sdk/src/contact.ts @@ -2,6 +2,7 @@ import { EventEmitter } from 'eventemitter3'; import type { Contact, Focus } from './api'; import { Logging } from './logging'; import { FocusModule } from './focus'; +import { LinkModule } from './link'; import type { Card, Channel, Article, Topic, Asset, Tag, FocusDetail, Profile, Participant } from './types'; import { type CardEntity, avatar } from './entities'; import type { ArticleDetail, ChannelSummary, ChannelDetail, CardProfile, CardDetail, CardItem, ChannelItem, ArticleItem } from './items'; @@ -845,6 +846,18 @@ export class ContactModule implements Contact { } } + public async callCard(cardId: string): Promise { + const { node, secure, token } = this; + const entry = this.cardEntries.get(cardId); + if (!entry || entry.item.detail.status !== 'connected') { + throw new Error('invalid card for call'); + } + const { profile, detail } = entry.item; + const link = new LinkModule(this.log); + await link.call(node, secure, token, cardId, profile.node, detail.token); + return link; + } + public async setBlockedCard(cardId: string, blocked: boolean): Promise { const entry = this.cardEntries.get(cardId); if (entry) { diff --git a/app/sdk/src/ring.ts b/app/sdk/src/ring.ts index 14132c0a..d70ac37f 100644 --- a/app/sdk/src/ring.ts +++ b/app/sdk/src/ring.ts @@ -1,39 +1,101 @@ import { EventEmitter } from 'eventemitter3'; -import type { Ring, Logging } from './api'; +import { LinkModule } from './link'; +import type { Ring, Link, Logging } from './api'; import type { Call } from './types'; +const EXPIRES = 6000; + export class RingModule implements Ring { private log: Logging; private emitter: EventEmitter; + private token: string; + private node: string; + private secure: boolean; + private calls: Map + private closed: boolean; constructor(log: Logging) { this.log = log; this.emitter = new EventEmitter(); + this.calls = new Map(); + this.ringing = []; + this.expire = null; + this.closed = false; } - public addCallingListener(ev: (calls: Call[]) => void): void { - this.emitter.on('calling', ev); + public addRingingListener(ev: (calls: { cardId: string, callId: string }[]) => void): void { + this.emitter.on('ringing', ev); } - public removeCallingListener(ev: (calls: Call[]) => void): void { - this.emitter.off('calling', ev); + public removeRingingListener(ev: (calls: { cardId: string, callId: string }[]) => void): void { + this.emitter.off('ringing', ev); } - public addCallListener(ev: (call: Call | null) => void): void { - this.emitter.on('call', ev); + public ring(call: Call): void { + const now = (new Date()).getTime(); + const { cardId, callId } = call; + const expires = now + EXPIRES; + const id = `${cardId}:${callId}`; + const ringing = this.calls.get(id); + if (ringing) { + ringing.expires = expires; + } else { + this.calls.set(id, { expires, call, status: 'ringing' }); + } + this.emitRinging(); + setTimeout(() => { this.emitRinging() }, EXPIRES + 1); } - public removeCallListener(ev: (call: Call | null) => void): void { - this.emitter.off('call', ev); + private emitRinging(): void { + if (!this.closed) { + const now = (new Date()).getTime(); + const ringing = Array.from(this.calls.values()); + this.emitter.emit('ringing', ringing.filter(item => item.expires > now && item.status === 'ringing').map(item => ({ cardId: item.call.cardId, callId: item.call.callId }))); + } } - public ring(call: Call): void {} + public async accept(cardId: string, callId: string, contactNode: string): Promise { + const now = (new Date()).getTime(); + const id = `${cardId}:${callId}`; + const entry = this.ringing.get(id); + if (!entry || entry.expires < now || entry.status !== 'ringing') { + throw new Error('invalid ringing entry'); + } + entry.status = 'accepted'; + this.emitRinging(); + const link = new LinkModule(this.log); + await link.join(contactNode, entry.call.calleeToken, ice); + return link; + } - public accept(callId: string): void {} + public async ignore(cardId: stirng, callId: string): Promise { + const id = `${cardId}:${callId}`; + const entry = this.ringing.get(id); + if (!entry || entry.expires < now || entry.status !== 'ringing') { + throw new Error('invalid ringing entry'); + } + entry.status = 'ignored'; + this.emitRinging(); + } - public ignore(callId: string): void {} + public async decline(cardId: string, callId: string, contactNode: string): Promise { + const id = `${cardId}:${callId}`; + const entry = this.ringing.get(id); + if (!entry || entry.expires < now || entry.status !== 'ringing') { + throw new Error('invalid ringing entry'); + } + try { + await removeContactCall(contactNode, entry.call.calleeToken, entry.call.callId); + } + catch (err) { + console.log(err); + } + entry.status = 'declined'; + this.emitRinging(); + } - public decline(callId: string): void {} - - public close(): void {} + public close(): void { + this.closed = true; + this.emitter.emit('ringing', []); + } } diff --git a/app/sdk/src/session.ts b/app/sdk/src/session.ts index c1973c7b..b12d7fb8 100644 --- a/app/sdk/src/session.ts +++ b/app/sdk/src/session.ts @@ -68,7 +68,7 @@ export class SessionModule implements Session { this.attribute = new AttributeModule(log, this.settings, this.store, guid, token, node, secure); this.stream = new StreamModule(log, this.store, this.crypto, this.staging, guid, token, node, secure, channelTypes); this.content = new ContentModule(log, this.crypto, this.contact, this.stream); - this.ring = new RingModule(log); + this.ring = new RingModule(log, token, node, secure); this.connection = new Connection(log, token, node, secure); const onStatus = (ev: string) => {