-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
1,185 additions
and
191 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,18 @@ | ||
{ | ||
"name": "@telnyx/sip", | ||
"version": "1.0.0", | ||
"main": "dist/js-sip/src/index.js", | ||
"main": "dist/index.js", | ||
"license": "MIT", | ||
"scripts": { | ||
"test": "echo \"Error: no test specified\" && exit 1", | ||
"build": "rm -rf ./dist && mkdir ./dist && tsc", | ||
"release": "release-it" | ||
}, | ||
"dependencies": { | ||
"sip.js": "^0.15.4" | ||
"eventemitter3": "^5.0.1", | ||
"sip.js": "^0.21.2" | ||
}, | ||
"devDependencies": { | ||
"typescript": "^5.4.2" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
import { Session } from "sip.js"; | ||
import { | ||
SessionDescriptionHandler, | ||
SessionManager, | ||
} from "sip.js/lib/platform/web"; | ||
import { SwEvent } from "./constant"; | ||
import { eventBus } from "./events"; | ||
import { CallState, ICall } from "./types"; | ||
|
||
export class Call implements ICall { | ||
private _session: Session; | ||
private _sessionManager: SessionManager; | ||
|
||
constructor( | ||
session: Session, | ||
manager: SessionManager, | ||
direction: "inbound" | "outbound" | ||
) { | ||
session.dialog?.id; | ||
this._session = session; | ||
this._sessionManager = manager; | ||
this.direction = direction; | ||
} | ||
|
||
public direction: "inbound" | "outbound"; | ||
public prevState: CallState; | ||
public state: CallState; | ||
|
||
public get id() { | ||
return this._session.id; | ||
} | ||
public get localStream(): MediaStream | null { | ||
return this._sessionManager.getLocalMediaStream(this._session) ?? null; | ||
} | ||
|
||
public get remoteStream(): MediaStream | null { | ||
return this._sessionManager.getRemoteMediaStream(this._session) ?? null; | ||
} | ||
|
||
public telnyxIDs: {}; | ||
|
||
public hangup(): Promise<void> { | ||
return this._sessionManager.hangup(this._session); | ||
} | ||
|
||
public setState(nextState: CallState) { | ||
this.prevState = this.state; | ||
this.state = nextState; | ||
|
||
eventBus.emit(SwEvent.Notification, { call: this, type: "callUpdate" }); | ||
} | ||
|
||
public answer(): Promise<void> { | ||
return this._sessionManager.answer(this._session).then(() => { | ||
this.setState("answering"); | ||
}); | ||
} | ||
public deaf(): void { | ||
const handler = this._session.sessionDescriptionHandler; | ||
if (!(handler instanceof SessionDescriptionHandler)) { | ||
return; | ||
} | ||
handler.enableReceiverTracks(false); | ||
} | ||
dtmf(dtmf: any): Promise<void> { | ||
throw new Error("Method not implemented."); | ||
} | ||
hold(): Promise<void> { | ||
return this._sessionManager.hold(this._session); | ||
} | ||
muteAudio(): Promise<void> { | ||
throw new Error("Method not implemented."); | ||
} | ||
muteVideo(): Promise<void> { | ||
throw new Error("Method not implemented."); | ||
} | ||
setAudioInDevice(deviceId: string): Promise<void> { | ||
throw new Error("Method not implemented."); | ||
} | ||
setAudioOutDevice(deviceId: string): Promise<void> { | ||
throw new Error("Method not implemented."); | ||
} | ||
setVideoDevice(deviceId: any): Promise<void> { | ||
throw new Error("Method not implemented."); | ||
} | ||
toggleAudioMute(): void { | ||
throw new Error("Method not implemented."); | ||
} | ||
toggleDeaf(): void { | ||
throw new Error("Method not implemented."); | ||
} | ||
toggleHold(): Promise<void> { | ||
throw new Error("Method not implemented."); | ||
} | ||
toggleVideoMute(): void { | ||
throw new Error("Method not implemented."); | ||
} | ||
undeaf(): void { | ||
const handler = this._session.sessionDescriptionHandler; | ||
if (!(handler instanceof SessionDescriptionHandler)) { | ||
return; | ||
} | ||
handler.enableReceiverTracks(true); | ||
} | ||
unhold(): Promise<void> { | ||
return this._sessionManager.unhold(this._session); | ||
} | ||
|
||
unmuteAudio(): void { | ||
const handler = this._session.sessionDescriptionHandler; | ||
if (!(handler instanceof SessionDescriptionHandler)) { | ||
return; | ||
} | ||
handler.enableSenderTracks(true); | ||
} | ||
unmuteVideo(): void { | ||
const handler = this._session.sessionDescriptionHandler; | ||
if (!(handler instanceof SessionDescriptionHandler)) { | ||
return; | ||
} | ||
handler.enableSenderTracks(true); | ||
} | ||
|
||
getStats() { | ||
console.log("getStats"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
import { Session, UserAgent } from "sip.js"; | ||
import { IncomingResponse } from "sip.js/lib/core"; | ||
import { SessionManager } from "sip.js/lib/platform/web"; | ||
import { Call } from "./call"; | ||
import { SwEvent } from "./constant"; | ||
import { eventBus } from "./events"; | ||
import { | ||
AnyFunction, | ||
ICall, | ||
ICallOptions, | ||
IClient, | ||
IClientOptions, | ||
IWebRTCInfo, | ||
IWebRTCSupportedBrowser, | ||
} from "./types"; | ||
|
||
export class Client implements IClient { | ||
public calls: Record<string, Call>; | ||
public options: IClientOptions; | ||
public mediaConstraints: MediaStreamConstraints; | ||
|
||
/** | ||
* @deprecated | ||
*/ | ||
public static telnyxStateCall(call: ICall) { | ||
return call; | ||
} | ||
private _sessionManager: SessionManager; | ||
|
||
constructor(options: IClientOptions) { | ||
this.calls = {}; | ||
this.options = options; | ||
this.mediaConstraints = { | ||
audio: true, | ||
video: false, | ||
}; | ||
|
||
this._sessionManager = new SessionManager("wss://sipdev.telnyx.com:7443", { | ||
aor: `sip:${options.login}@sip.telnyx.com`, | ||
delegate: { | ||
onRegistered: this._onRegister, | ||
onUnregistered: this._onUnregister, | ||
onServerConnect: this._onSocketOpen, | ||
onServerDisconnect: this._onSocketClose, | ||
onCallAnswered: this._onCallAnswered, | ||
onCallReceived: this._onCallReceived, | ||
onCallHangup: this._onCallHangup, | ||
}, | ||
userAgentOptions: { | ||
logLevel: "warn", | ||
authorizationUsername: options.login, | ||
authorizationPassword: options.password, | ||
uri: UserAgent.makeURI(`sip:${options.login}@sip.telnyx.com`), | ||
displayName: options.login, | ||
sessionDescriptionHandlerFactoryOptions: { | ||
iceGatheringTimeout: 1000, | ||
peerConnectionConfiguration: { | ||
bundlePolicy: "max-compat", | ||
sdpSemantics: "unified-plan", | ||
rtcpMuxPolicy: "negotiate", | ||
}, | ||
}, | ||
}, | ||
}); | ||
} | ||
|
||
private _onCallHangup = (session: Session) => { | ||
const call = this.calls[session.id]; | ||
if (!call) { | ||
return; | ||
} | ||
call.setState("hangup"); | ||
delete this.calls[call.id]; | ||
}; | ||
private _onCallReceived = (session: Session) => { | ||
const call = new Call(session, this._sessionManager, "inbound"); | ||
this.calls[session.id] = call; | ||
call.setState("ringing"); | ||
}; | ||
async checkPermissions(audio: boolean, video: boolean): Promise<boolean> { | ||
return true; | ||
} | ||
disableMicrophone(): void { | ||
this.mediaConstraints.audio = false; | ||
} | ||
disableWebcam(): void { | ||
this.mediaConstraints.video = false; | ||
} | ||
enableMicrophone(): void { | ||
this.mediaConstraints.audio = true; | ||
} | ||
enableWebcam(): void { | ||
this.mediaConstraints.video = true; | ||
} | ||
|
||
async logout(): Promise<void> {} | ||
|
||
private _onCallTrying = (response: IncomingResponse) => { | ||
const id = response.message.callId + response.message.fromTag; | ||
const call = this.calls[id]; | ||
if (!call) { | ||
return; | ||
} | ||
call.setState("trying"); | ||
}; | ||
|
||
private _onCallProgress = (response: IncomingResponse) => { | ||
const id = response.message.callId + response.message.fromTag; | ||
const call = this.calls[id]; | ||
if (!call) { | ||
return; | ||
} | ||
if (response.message.statusCode === 180) { | ||
call.setState("ringing"); | ||
} | ||
}; | ||
|
||
async newCall(options: ICallOptions): Promise<Call> { | ||
const extraHeaders = options.customHeaders | ||
? options.customHeaders.map(({ name, value }) => `${name}: ${value}`) | ||
: []; | ||
|
||
const session = await this._sessionManager.call( | ||
options.destinationNumber, | ||
{ | ||
earlyMedia: true, | ||
extraHeaders: extraHeaders, | ||
}, | ||
{ | ||
requestDelegate: { | ||
onTrying: this._onCallTrying, | ||
onProgress: this._onCallProgress, | ||
}, | ||
} | ||
); | ||
const call = new Call(session, this._sessionManager, "outbound"); | ||
return call; | ||
} | ||
|
||
private _onCallAnswered = (session: Session) => { | ||
const call = this.calls[session.id]; | ||
if (!call) { | ||
return; | ||
} | ||
call.setState("active"); | ||
}; | ||
|
||
off(eventName: string, callback: AnyFunction): void { | ||
eventBus.removeListener(eventName, callback); | ||
} | ||
on(eventName: string, callback: AnyFunction): void { | ||
eventBus.addListener(eventName, callback); | ||
} | ||
|
||
private _onRegister = () => { | ||
eventBus.emit(SwEvent.Ready); | ||
}; | ||
private _onUnregister = () => { | ||
eventBus.emit(SwEvent.Closed); | ||
}; | ||
|
||
private _onSocketOpen = () => { | ||
eventBus.emit(SwEvent.SocketOpen); | ||
}; | ||
private _onSocketClose = () => { | ||
eventBus.emit(SwEvent.SocketClose); | ||
}; | ||
|
||
public get connected() { | ||
return this._sessionManager.isConnected(); | ||
} | ||
|
||
public async connect() { | ||
await this._sessionManager.connect(); | ||
await this._sessionManager.register(); | ||
} | ||
|
||
public async disconnect() { | ||
await this._sessionManager.unregister(); | ||
await this._sessionManager.disconnect(); | ||
} | ||
|
||
// ---------- | ||
|
||
localElement: HTMLMediaElement | null; | ||
remoteElement: HTMLMediaElement | null; | ||
speaker: string | null; | ||
|
||
webRTCInfo(): IWebRTCInfo { | ||
return {} as IWebRTCInfo; | ||
} | ||
webRTCSupportedBrowserList(): IWebRTCSupportedBrowser[] { | ||
return []; | ||
} | ||
|
||
async getAudioInDevices(): Promise<MediaDeviceInfo[]> { | ||
return []; | ||
} | ||
async getAudioOutDevices(): Promise<MediaDeviceInfo[]> { | ||
return []; | ||
} | ||
getDeviceResolutions( | ||
deviceId: string | ||
): { resolution: string; width: number; height: number }[] { | ||
return []; | ||
} | ||
async getDevices(): Promise<MediaDeviceInfo[]> { | ||
return []; | ||
} | ||
async getVideoDevices(): Promise<MediaDeviceInfo[]> { | ||
return []; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
export const TELNYX_WS_URL_PROD: string = "wss://sip.telnyx.com"; | ||
export const TELNYX_WS_URL_DEV: string = "wss://sipdev.telnyx.com:7443"; | ||
export enum SwEvent { | ||
// Socket Events | ||
SocketOpen = "telnyx.socket.open", | ||
SocketClose = "telnyx.socket.close", | ||
SocketError = "telnyx.socket.error", | ||
SocketMessage = "telnyx.socket.message", | ||
|
||
// Internal events | ||
SpeedTest = "telnyx.internal.speedtest", | ||
|
||
// Global Events | ||
Ready = "telnyx.ready", | ||
Closed = "telnyx.closed", | ||
Error = "telnyx.error", | ||
Notification = "telnyx.notification", | ||
|
||
// Blade Events | ||
Messages = "telnyx.messages", | ||
Calls = "telnyx.calls", | ||
|
||
// RTC Events | ||
MediaError = "telnyx.rtc.mediaError", | ||
} | ||
|
||
const stunServers = { | ||
urls: ["stun:stun.telnyx.com:3478"], | ||
}; | ||
const turnServers = { | ||
urls: ["turn:turn.telnyx.com:3478?transport=tcp"], | ||
username: "turnuser", | ||
password: "turnpassword", | ||
}; |
Oops, something went wrong.