preparing webrtc connection

This commit is contained in:
balzack 2025-01-22 22:36:59 -08:00
parent 9d68e4aa49
commit 72df16d803
4 changed files with 110 additions and 24 deletions

View File

@ -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<void>;
close(): Promise<void>;
}
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<Link>;
decline(cardId: string, callId: string, contactNode: string): Promise<void>;
ignore(cardId: string, callId: string): Promise<void>;
}
export interface Settings {
@ -75,6 +84,8 @@ export interface Contact {
setBlockedCard(cardId: string, blocked: boolean): Promise<void>;
getRegistry(handle: string | null, server: string | null): Promise<Profile[]>;
callCard(cardId: string, ringInterval: number): Promise<Link>;
addCardListener(ev: (cards: Card[]) => void): void;
removeCardListener(ev: (cards: Card[]) => void): void;
}

View File

@ -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<Link> {
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<void> {
const entry = this.cardEntries.get(cardId);
if (entry) {

View File

@ -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<string, { call: Call, expires: number, status: string }>
private closed: boolean;
constructor(log: Logging) {
this.log = log;
this.emitter = new EventEmitter();
this.calls = new Map<string, { call: Call, expires: number }>();
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<Link> {
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<void> {
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<void> {
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', []);
}
}

View File

@ -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) => {