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; 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 { export interface Ring {
addCallingListener(ev: (calls: Call[]) => void): void; addRingingListener(ev: (calls: Call[]) => void): void;
removeCallingListener(ev: (calls: Call[]) => void): void; removeRingingListener(ev: (calls: Call[]) => void): void;
addCallListener(ev: (call: Call | null) => void): void; accept(cardId: string, callId: string, contactNode: string): Promise<Link>;
removeCallListener(ev: (call: Call | null) => void): void; decline(cardId: string, callId: string, contactNode: string): Promise<void>;
ignore(cardId: string, callId: string): Promise<void>;
accept(callId: string): void;
ignore(callId: string): void;
decline(callId: string): void;
} }
export interface Settings { export interface Settings {
@ -75,6 +84,8 @@ export interface Contact {
setBlockedCard(cardId: string, blocked: boolean): Promise<void>; setBlockedCard(cardId: string, blocked: boolean): Promise<void>;
getRegistry(handle: string | null, server: string | null): Promise<Profile[]>; getRegistry(handle: string | null, server: string | null): Promise<Profile[]>;
callCard(cardId: string, ringInterval: number): Promise<Link>;
addCardListener(ev: (cards: Card[]) => void): void; addCardListener(ev: (cards: Card[]) => void): void;
removeCardListener(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 type { Contact, Focus } from './api';
import { Logging } from './logging'; import { Logging } from './logging';
import { FocusModule } from './focus'; import { FocusModule } from './focus';
import { LinkModule } from './link';
import type { Card, Channel, Article, Topic, Asset, Tag, FocusDetail, Profile, Participant } from './types'; import type { Card, Channel, Article, Topic, Asset, Tag, FocusDetail, Profile, Participant } from './types';
import { type CardEntity, avatar } from './entities'; import { type CardEntity, avatar } from './entities';
import type { ArticleDetail, ChannelSummary, ChannelDetail, CardProfile, CardDetail, CardItem, ChannelItem, ArticleItem } from './items'; 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> { public async setBlockedCard(cardId: string, blocked: boolean): Promise<void> {
const entry = this.cardEntries.get(cardId); const entry = this.cardEntries.get(cardId);
if (entry) { if (entry) {

View File

@ -1,39 +1,101 @@
import { EventEmitter } from 'eventemitter3'; 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'; import type { Call } from './types';
const EXPIRES = 6000;
export class RingModule implements Ring { export class RingModule implements Ring {
private log: Logging; private log: Logging;
private emitter: EventEmitter; 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) { constructor(log: Logging) {
this.log = log; this.log = log;
this.emitter = new EventEmitter(); 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 { public addRingingListener(ev: (calls: { cardId: string, callId: string }[]) => void): void {
this.emitter.on('calling', ev); this.emitter.on('ringing', ev);
} }
public removeCallingListener(ev: (calls: Call[]) => void): void { public removeRingingListener(ev: (calls: { cardId: string, callId: string }[]) => void): void {
this.emitter.off('calling', ev); this.emitter.off('ringing', ev);
} }
public addCallListener(ev: (call: Call | null) => void): void { public ring(call: Call): void {
this.emitter.on('call', ev); 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 { private emitRinging(): void {
this.emitter.off('call', ev); 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();
public accept(callId: string): void {} const id = `${cardId}:${callId}`;
const entry = this.ringing.get(id);
public ignore(callId: string): void {} if (!entry || entry.expires < now || entry.status !== 'ringing') {
throw new Error('invalid ringing entry');
public decline(callId: string): void {} }
entry.status = 'accepted';
public close(): void {} this.emitRinging();
const link = new LinkModule(this.log);
await link.join(contactNode, entry.call.calleeToken, ice);
return link;
}
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 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 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.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.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.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); this.connection = new Connection(log, token, node, secure);
const onStatus = (ev: string) => { const onStatus = (ev: string) => {