node-datachannel
Advanced tools
Sorry, the diff of this file is not supported yet
+5
-1
@@ -5,4 +5,8 @@ cmake_minimum_required(VERSION 3.15) | ||
| project(node_datachannel VERSION 0.29.0) | ||
| project(node_datachannel VERSION 0.30.0) | ||
| # compile_commands.json | ||
| # ----------------------------------- | ||
| set(CMAKE_EXPORT_COMPILE_COMMANDS True) | ||
| # -Dnapi_build_version=8 | ||
@@ -9,0 +13,0 @@ add_definitions(-DNAPI_VERSION=8) |
@@ -290,3 +290,8 @@ 'use strict'; | ||
| createDataChannel(label, opts = {}) { | ||
| const channel = __privateGet(this, _peerConnection).createDataChannel(label, opts); | ||
| const nativeOpts = { | ||
| ...opts, | ||
| // libdatachannel uses "unordered", opposite of RTCDataChannelInit.ordered | ||
| unordered: opts.ordered === false ? true : false | ||
| }; | ||
| const channel = __privateGet(this, _peerConnection).createDataChannel(label, nativeOpts); | ||
| const dataChannel = new RTCDataChannel.default(channel, opts); | ||
@@ -293,0 +298,0 @@ __privateGet(this, _dataChannels).add(dataChannel); |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"RTCPeerConnection.cjs","sources":["../../../src/polyfill/RTCPeerConnection.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { SelectedCandidateInfo } from '../lib/types';\nimport { PeerConnection } from '../lib/index';\nimport RTCSessionDescription from './RTCSessionDescription';\nimport RTCDataChannel from './RTCDataChannel';\nimport RTCIceCandidate from './RTCIceCandidate';\nimport { RTCDataChannelEvent, RTCPeerConnectionIceEvent } from './Events';\nimport RTCSctpTransport from './RTCSctpTransport';\nimport * as exceptions from './Exception';\nimport RTCCertificate from './RTCCertificate';\n\n// extend RTCConfiguration with peerIdentity\ninterface RTCConfiguration extends globalThis.RTCConfiguration {\n peerIdentity?: string;\n peerConnection?: PeerConnection;\n}\n\nexport default class RTCPeerConnection extends EventTarget implements globalThis.RTCPeerConnection {\n static async generateCertificate(): Promise<RTCCertificate> {\n throw new DOMException('Not implemented');\n }\n\n #peerConnection: PeerConnection;\n #localOffer: ReturnType<typeof createDeferredPromise>;\n #localAnswer: ReturnType<typeof createDeferredPromise>;\n #dataChannels: Set<globalThis.RTCDataChannel>;\n #dataChannelsClosed = 0;\n #config: globalThis.RTCConfiguration;\n #canTrickleIceCandidates: boolean | null = null;\n #sctp: globalThis.RTCSctpTransport;\n #announceNegotiation = false;\n\n #localCandidates: globalThis.RTCIceCandidate[] = [];\n #remoteCandidates: globalThis.RTCIceCandidate[] = [];\n\n // events\n onconnectionstatechange: globalThis.RTCPeerConnection['onconnectionstatechange'] = null;\n // For ondatachannel we need to define type manually\n ondatachannel: ((this: globalThis.RTCPeerConnection, ev: RTCDataChannelEvent) => any) | null;\n onicecandidate: globalThis.RTCPeerConnection['onicecandidate'] = null;\n onicecandidateerror: globalThis.RTCPeerConnection['onicecandidateerror'] = null;\n oniceconnectionstatechange: globalThis.RTCPeerConnection['oniceconnectionstatechange'] = null;\n onicegatheringstatechange: globalThis.RTCPeerConnection['onicegatheringstatechange'] = null;\n onnegotiationneeded: globalThis.RTCPeerConnection['onnegotiationneeded'] = null;\n onsignalingstatechange: globalThis.RTCPeerConnection['onsignalingstatechange'] = null;\n ontrack: globalThis.RTCPeerConnection['ontrack'] = null;\n\n private _checkConfiguration(config: globalThis.RTCConfiguration): void {\n if (config && config.iceServers === undefined) config.iceServers = [];\n if (config && config.iceTransportPolicy === undefined) config.iceTransportPolicy = 'all';\n\n if (config?.iceServers === null) throw new TypeError('IceServers cannot be null');\n\n // Check for all the properties of iceServers\n if (Array.isArray(config?.iceServers)) {\n for (let i = 0; i < config.iceServers.length; i++) {\n if (config.iceServers[i] === null) throw new TypeError('IceServers cannot be null');\n if (config.iceServers[i] === undefined)\n throw new TypeError('IceServers cannot be undefined');\n if (Object.keys(config.iceServers[i]).length === 0)\n throw new TypeError('IceServers cannot be empty');\n\n // If iceServers is string convert to array\n if (typeof config.iceServers[i].urls === 'string')\n config.iceServers[i].urls = [config.iceServers[i].urls as string];\n\n // urls can not be empty\n if ((config.iceServers[i].urls as string[])?.some((url) => url == ''))\n throw new exceptions.SyntaxError('IceServers urls cannot be empty');\n\n // urls should be valid URLs and match the protocols \"stun:|turn:|turns:\"\n if (\n (config.iceServers[i].urls as string[])?.some((url) => {\n try {\n const parsedURL = new URL(url);\n\n return !/^(stun:|turn:|turns:)$/.test(parsedURL.protocol);\n } catch (error) {\n return true;\n }\n })\n )\n throw new exceptions.SyntaxError('IceServers urls wrong format');\n\n // If this is a turn server check for username and credential\n if ((config.iceServers[i].urls as string[])?.some((url) => url.startsWith('turn'))) {\n if (!config.iceServers[i].username)\n throw new exceptions.InvalidAccessError('IceServers username cannot be null');\n if (!config.iceServers[i].credential)\n throw new exceptions.InvalidAccessError('IceServers username cannot be undefined');\n }\n\n // length of urls can not be 0\n if (config.iceServers[i].urls?.length === 0)\n throw new exceptions.SyntaxError('IceServers urls cannot be empty');\n }\n }\n\n if (\n config &&\n config.iceTransportPolicy &&\n config.iceTransportPolicy !== 'all' &&\n config.iceTransportPolicy !== 'relay'\n )\n throw new TypeError('IceTransportPolicy must be either \"all\" or \"relay\"');\n }\n\n setConfiguration(config: globalThis.RTCConfiguration): void {\n this._checkConfiguration(config);\n this.#config = config;\n }\n\n constructor(config: RTCConfiguration = { iceServers: [], iceTransportPolicy: 'all' }) {\n super();\n\n this._checkConfiguration(config);\n this.#config = config;\n this.#localOffer = createDeferredPromise();\n this.#localAnswer = createDeferredPromise();\n this.#dataChannels = new Set();\n this.#canTrickleIceCandidates = null;\n\n try {\n const peerIdentity = (config as any)?.peerIdentity ?? `peer-${getRandomString(7)}`;\n this.#peerConnection =\n config?.peerConnection ??\n new PeerConnection(peerIdentity, {\n ...config,\n iceServers:\n config?.iceServers\n ?.map((server) => {\n const urls = Array.isArray(server.urls) ? server.urls : [server.urls];\n\n return urls.map((url) => {\n if (server.username && server.credential) {\n const [protocol, rest] = url.split(/:(.*)/);\n return `${protocol}:${server.username}:${server.credential}@${rest}`;\n }\n return url;\n });\n })\n .flat() ?? [],\n });\n } catch (error) {\n if (!error || !error.message) throw new exceptions.NotFoundError('Unknown error');\n throw new exceptions.SyntaxError(error.message);\n }\n\n // forward peerConnection events\n this.#peerConnection.onStateChange(() => {\n this.dispatchEvent(new Event('connectionstatechange'));\n });\n\n this.#peerConnection.onIceStateChange(() => {\n this.dispatchEvent(new Event('iceconnectionstatechange'));\n });\n\n this.#peerConnection.onSignalingStateChange(() => {\n this.dispatchEvent(new Event('signalingstatechange'));\n });\n\n this.#peerConnection.onGatheringStateChange(() => {\n this.dispatchEvent(new Event('icegatheringstatechange'));\n });\n\n this.#peerConnection.onDataChannel((channel) => {\n const dc = new RTCDataChannel(channel);\n this.#dataChannels.add(dc);\n this.dispatchEvent(new RTCDataChannelEvent('datachannel', { channel: dc }));\n });\n\n this.#peerConnection.onLocalDescription((sdp, type) => {\n if (type === 'offer') {\n this.#localOffer.resolve(new RTCSessionDescription({ sdp, type }));\n }\n\n if (type === 'answer') {\n this.#localAnswer.resolve(new RTCSessionDescription({ sdp, type }));\n }\n });\n\n this.#peerConnection.onLocalCandidate((candidate, sdpMid) => {\n if (sdpMid === 'unspec') {\n this.#localAnswer.reject(new Error(`Invalid description type ${sdpMid}`));\n return;\n }\n\n this.#localCandidates.push(new RTCIceCandidate({ candidate, sdpMid }));\n this.dispatchEvent(new RTCPeerConnectionIceEvent(new RTCIceCandidate({ candidate, sdpMid })));\n });\n\n // forward events to properties\n this.addEventListener('connectionstatechange', (e) => {\n this.onconnectionstatechange?.(e);\n });\n this.addEventListener('signalingstatechange', (e) => {\n this.onsignalingstatechange?.(e);\n });\n this.addEventListener('iceconnectionstatechange', (e) => {\n this.oniceconnectionstatechange?.(e);\n });\n this.addEventListener('icegatheringstatechange', (e) => {\n this.onicegatheringstatechange?.(e);\n });\n this.addEventListener('datachannel', (e) => {\n this.ondatachannel?.(e as RTCDataChannelEvent);\n });\n this.addEventListener('icecandidate', (e) => {\n this.onicecandidate?.(e as globalThis.RTCPeerConnectionIceEvent);\n });\n this.addEventListener('track', (e) => {\n this.ontrack?.(e as RTCTrackEvent);\n });\n this.addEventListener('negotiationneeded', (e) => {\n this.#announceNegotiation = true;\n this.onnegotiationneeded?.(e);\n });\n\n this.#sctp = new RTCSctpTransport({\n pc: this,\n });\n }\n\n // Extra FUnctions\n get ext_maxDataChannelId(): number {\n return this.#peerConnection.maxDataChannelId();\n }\n\n get ext_maxMessageSize(): number {\n return this.#peerConnection.maxMessageSize();\n }\n\n get ext_localCandidates(): globalThis.RTCIceCandidate[] {\n return this.#localCandidates;\n }\n\n get ext_remoteCandidates(): globalThis.RTCIceCandidate[] {\n return this.#remoteCandidates;\n }\n\n selectedCandidatePair(): {\n local: SelectedCandidateInfo;\n remote: SelectedCandidateInfo;\n } | null {\n return this.#peerConnection.getSelectedCandidatePair();\n }\n\n get canTrickleIceCandidates(): boolean | null {\n return this.#canTrickleIceCandidates;\n }\n\n get connectionState(): globalThis.RTCPeerConnectionState {\n return this.#peerConnection.state();\n }\n\n get iceConnectionState(): globalThis.RTCIceConnectionState {\n let state = this.#peerConnection.iceState();\n // libdatachannel uses 'completed' instead of 'connected'\n // see /webrtc/getstats.html\n if (state == 'completed') state = 'connected';\n return state;\n }\n\n get iceGatheringState(): globalThis.RTCIceGatheringState {\n return this.#peerConnection.gatheringState();\n }\n\n get currentLocalDescription(): globalThis.RTCSessionDescription {\n return new RTCSessionDescription(this.#peerConnection.localDescription() as any);\n }\n\n get currentRemoteDescription(): globalThis.RTCSessionDescription {\n return new RTCSessionDescription(this.#peerConnection.remoteDescription() as any);\n }\n\n get localDescription(): globalThis.RTCSessionDescription {\n return new RTCSessionDescription(this.#peerConnection.localDescription() as any);\n }\n\n get pendingLocalDescription(): globalThis.RTCSessionDescription {\n return new RTCSessionDescription(this.#peerConnection.localDescription() as any);\n }\n\n get pendingRemoteDescription(): globalThis.RTCSessionDescription {\n return new RTCSessionDescription(this.#peerConnection.remoteDescription() as any);\n }\n\n get remoteDescription(): globalThis.RTCSessionDescription {\n return new RTCSessionDescription(this.#peerConnection.remoteDescription() as any);\n }\n\n get sctp(): globalThis.RTCSctpTransport {\n return this.#sctp;\n }\n\n get signalingState(): globalThis.RTCSignalingState {\n return this.#peerConnection.signalingState();\n }\n\n async addIceCandidate(candidate?: globalThis.RTCIceCandidateInit | null): Promise<void> {\n if (!candidate || !candidate.candidate) {\n return;\n }\n\n if (candidate.sdpMid === null && candidate.sdpMLineIndex === null) {\n throw new TypeError('sdpMid must be set');\n }\n\n if (candidate.sdpMid === undefined && candidate.sdpMLineIndex == undefined) {\n throw new TypeError('sdpMid must be set');\n }\n\n // Reject if sdpMid format is not valid\n // ??\n if (candidate.sdpMid && candidate.sdpMid.length > 3) {\n // console.log(candidate.sdpMid);\n throw new exceptions.OperationError('Invalid sdpMid format');\n }\n\n // We don't care about sdpMLineIndex, just for test\n if (!candidate.sdpMid && candidate.sdpMLineIndex > 1) {\n throw new exceptions.OperationError('This is only for test case.');\n }\n\n try {\n this.#peerConnection.addRemoteCandidate(candidate.candidate, candidate.sdpMid ?? '0');\n this.#remoteCandidates.push(\n new RTCIceCandidate({\n candidate: candidate.candidate,\n sdpMid: candidate.sdpMid ?? '0',\n }),\n );\n } catch (error) {\n if (!error || !error.message) throw new exceptions.NotFoundError('Unknown error');\n\n // Check error Message if contains specific message\n if (error.message.includes('remote candidate without remote description'))\n throw new exceptions.InvalidStateError(error.message);\n if (error.message.includes('Invalid candidate format'))\n throw new exceptions.OperationError(error.message);\n\n throw new exceptions.NotFoundError(error.message);\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n addTrack(_track, ..._streams): globalThis.RTCRtpSender {\n throw new DOMException('Not implemented');\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n addTransceiver(_trackOrKind, _init): globalThis.RTCRtpTransceiver {\n throw new DOMException('Not implemented');\n }\n\n close(): void {\n this.#peerConnection.close();\n }\n\n createAnswer(): Promise<globalThis.RTCSessionDescriptionInit | any> {\n return this.#localAnswer;\n }\n\n createDataChannel(label: string, opts: globalThis.RTCDataChannelInit = {}): RTCDataChannel {\n const channel = this.#peerConnection.createDataChannel(label, opts);\n const dataChannel = new RTCDataChannel(channel, opts);\n\n // ensure we can close all channels when shutting down\n this.#dataChannels.add(dataChannel);\n dataChannel.addEventListener('close', () => {\n this.#dataChannels.delete(dataChannel);\n this.#dataChannelsClosed++;\n });\n\n if (this.#announceNegotiation == null) {\n this.#announceNegotiation = false;\n this.dispatchEvent(new Event('negotiationneeded'));\n }\n\n return dataChannel;\n }\n\n createOffer(): Promise<globalThis.RTCSessionDescriptionInit | any> {\n return this.#localOffer;\n }\n\n getConfiguration(): globalThis.RTCConfiguration {\n return this.#config;\n }\n\n getReceivers(): globalThis.RTCRtpReceiver[] {\n throw new DOMException('Not implemented');\n }\n\n getSenders(): globalThis.RTCRtpSender[] {\n throw new DOMException('Not implemented');\n }\n\n getStats(): Promise<globalThis.RTCStatsReport> | any {\n return new Promise((resolve) => {\n const report = new Map();\n const cp = this.#peerConnection?.getSelectedCandidatePair();\n const bytesSent = this.#peerConnection?.bytesSent();\n const bytesReceived = this.#peerConnection?.bytesReceived();\n const rtt = this.#peerConnection?.rtt();\n\n if (!cp) {\n return resolve(report);\n }\n\n const localIdRs = getRandomString(8);\n const localId = 'RTCIceCandidate_' + localIdRs;\n report.set(localId, {\n id: localId,\n type: 'local-candidate',\n timestamp: Date.now(),\n candidateType: cp.local.type,\n ip: cp.local.address,\n port: cp.local.port,\n });\n\n const remoteIdRs = getRandomString(8);\n const remoteId = 'RTCIceCandidate_' + remoteIdRs;\n report.set(remoteId, {\n id: remoteId,\n type: 'remote-candidate',\n timestamp: Date.now(),\n candidateType: cp.remote.type,\n ip: cp.remote.address,\n port: cp.remote.port,\n });\n\n const candidateId = 'RTCIceCandidatePair_' + localIdRs + '_' + remoteIdRs;\n report.set(candidateId, {\n id: candidateId,\n type: 'candidate-pair',\n timestamp: Date.now(),\n localCandidateId: localId,\n remoteCandidateId: remoteId,\n state: 'succeeded',\n nominated: true,\n writable: true,\n bytesSent: bytesSent,\n bytesReceived: bytesReceived,\n totalRoundTripTime: rtt,\n currentRoundTripTime: rtt,\n });\n\n const transportId = 'RTCTransport_0_1';\n report.set(transportId, {\n id: transportId,\n timestamp: Date.now(),\n type: 'transport',\n bytesSent: bytesSent,\n bytesReceived: bytesReceived,\n dtlsState: 'connected',\n selectedCandidatePairId: candidateId,\n selectedCandidatePairChanges: 1,\n });\n\n // peer-connection'\n report.set('P', {\n id: 'P',\n type: 'peer-connection',\n timestamp: Date.now(),\n dataChannelsOpened: this.#dataChannels.size,\n dataChannelsClosed: this.#dataChannelsClosed,\n });\n\n return resolve(report);\n });\n }\n\n getTransceivers(): globalThis.RTCRtpTransceiver[] {\n return []; // throw new DOMException('Not implemented');\n }\n\n removeTrack(): void {\n throw new DOMException('Not implemented');\n }\n\n restartIce(): Promise<void> {\n throw new DOMException('Not implemented');\n }\n\n async setLocalDescription(description: globalThis.RTCSessionDescriptionInit): Promise<void> {\n if (description?.type !== 'offer') {\n // any other type causes libdatachannel to throw\n return;\n }\n\n this.#peerConnection.setLocalDescription(description?.type as any);\n }\n\n async setRemoteDescription(description: globalThis.RTCSessionDescriptionInit): Promise<void> {\n if (description.sdp == null) {\n throw new DOMException('Remote SDP must be set');\n }\n\n this.#peerConnection.setRemoteDescription(description.sdp, description.type as any);\n }\n}\n\nfunction createDeferredPromise(): any {\n let resolve: any, reject: any;\n\n const promise = new Promise(function (_resolve, _reject) {\n resolve = _resolve;\n reject = _reject;\n });\n\n (promise as any).resolve = resolve;\n (promise as any).reject = reject;\n return promise;\n}\n\nfunction getRandomString(length: number): string {\n return Math.random()\n .toString(36)\n .substring(2, 2 + length);\n}\n"],"names":["PeerConnection","exceptions.NotFoundError","exceptions.SyntaxError","RTCDataChannel","RTCDataChannelEvent","RTCSessionDescription","RTCIceCandidate","RTCPeerConnectionIceEvent","RTCSctpTransport","exceptions.InvalidAccessError","exceptions.OperationError","exceptions.InvalidStateError"],"mappings":"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"} | ||
| {"version":3,"file":"RTCPeerConnection.cjs","sources":["../../../src/polyfill/RTCPeerConnection.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { DataChannelInitConfig, SelectedCandidateInfo } from '../lib/types';\nimport { PeerConnection } from '../lib/index';\nimport RTCSessionDescription from './RTCSessionDescription';\nimport RTCDataChannel from './RTCDataChannel';\nimport RTCIceCandidate from './RTCIceCandidate';\nimport { RTCDataChannelEvent, RTCPeerConnectionIceEvent } from './Events';\nimport RTCSctpTransport from './RTCSctpTransport';\nimport * as exceptions from './Exception';\nimport RTCCertificate from './RTCCertificate';\n\n// extend RTCConfiguration with peerIdentity\ninterface RTCConfiguration extends globalThis.RTCConfiguration {\n peerIdentity?: string;\n peerConnection?: PeerConnection;\n}\n\nexport default class RTCPeerConnection extends EventTarget implements globalThis.RTCPeerConnection {\n static async generateCertificate(): Promise<RTCCertificate> {\n throw new DOMException('Not implemented');\n }\n\n #peerConnection: PeerConnection;\n #localOffer: ReturnType<typeof createDeferredPromise>;\n #localAnswer: ReturnType<typeof createDeferredPromise>;\n #dataChannels: Set<globalThis.RTCDataChannel>;\n #dataChannelsClosed = 0;\n #config: globalThis.RTCConfiguration;\n #canTrickleIceCandidates: boolean | null = null;\n #sctp: globalThis.RTCSctpTransport;\n #announceNegotiation = false;\n\n #localCandidates: globalThis.RTCIceCandidate[] = [];\n #remoteCandidates: globalThis.RTCIceCandidate[] = [];\n\n // events\n onconnectionstatechange: globalThis.RTCPeerConnection['onconnectionstatechange'] = null;\n // For ondatachannel we need to define type manually\n ondatachannel: ((this: globalThis.RTCPeerConnection, ev: globalThis.RTCDataChannelEvent) => any) | null;\n onicecandidate: globalThis.RTCPeerConnection['onicecandidate'] = null;\n onicecandidateerror: globalThis.RTCPeerConnection['onicecandidateerror'] = null;\n oniceconnectionstatechange: globalThis.RTCPeerConnection['oniceconnectionstatechange'] = null;\n onicegatheringstatechange: globalThis.RTCPeerConnection['onicegatheringstatechange'] = null;\n onnegotiationneeded: globalThis.RTCPeerConnection['onnegotiationneeded'] = null;\n onsignalingstatechange: globalThis.RTCPeerConnection['onsignalingstatechange'] = null;\n ontrack: globalThis.RTCPeerConnection['ontrack'] = null;\n\n private _checkConfiguration(config: globalThis.RTCConfiguration): void {\n if (config && config.iceServers === undefined) config.iceServers = [];\n if (config && config.iceTransportPolicy === undefined) config.iceTransportPolicy = 'all';\n\n if (config?.iceServers === null) throw new TypeError('IceServers cannot be null');\n\n // Check for all the properties of iceServers\n if (Array.isArray(config?.iceServers)) {\n for (let i = 0; i < config.iceServers.length; i++) {\n if (config.iceServers[i] === null) throw new TypeError('IceServers cannot be null');\n if (config.iceServers[i] === undefined)\n throw new TypeError('IceServers cannot be undefined');\n if (Object.keys(config.iceServers[i]).length === 0)\n throw new TypeError('IceServers cannot be empty');\n\n // If iceServers is string convert to array\n if (typeof config.iceServers[i].urls === 'string')\n config.iceServers[i].urls = [config.iceServers[i].urls as string];\n\n // urls can not be empty\n if ((config.iceServers[i].urls as string[])?.some((url) => url == ''))\n throw new exceptions.SyntaxError('IceServers urls cannot be empty');\n\n // urls should be valid URLs and match the protocols \"stun:|turn:|turns:\"\n if (\n (config.iceServers[i].urls as string[])?.some((url) => {\n try {\n const parsedURL = new URL(url);\n\n return !/^(stun:|turn:|turns:)$/.test(parsedURL.protocol);\n } catch (error) {\n return true;\n }\n })\n )\n throw new exceptions.SyntaxError('IceServers urls wrong format');\n\n // If this is a turn server check for username and credential\n if ((config.iceServers[i].urls as string[])?.some((url) => url.startsWith('turn'))) {\n if (!config.iceServers[i].username)\n throw new exceptions.InvalidAccessError('IceServers username cannot be null');\n if (!config.iceServers[i].credential)\n throw new exceptions.InvalidAccessError('IceServers username cannot be undefined');\n }\n\n // length of urls can not be 0\n if (config.iceServers[i].urls?.length === 0)\n throw new exceptions.SyntaxError('IceServers urls cannot be empty');\n }\n }\n\n if (\n config &&\n config.iceTransportPolicy &&\n config.iceTransportPolicy !== 'all' &&\n config.iceTransportPolicy !== 'relay'\n )\n throw new TypeError('IceTransportPolicy must be either \"all\" or \"relay\"');\n }\n\n setConfiguration(config: globalThis.RTCConfiguration): void {\n this._checkConfiguration(config);\n this.#config = config;\n }\n\n constructor(config: RTCConfiguration = { iceServers: [], iceTransportPolicy: 'all' }) {\n super();\n\n this._checkConfiguration(config);\n this.#config = config;\n this.#localOffer = createDeferredPromise();\n this.#localAnswer = createDeferredPromise();\n this.#dataChannels = new Set();\n this.#canTrickleIceCandidates = null;\n\n try {\n const peerIdentity = (config as any)?.peerIdentity ?? `peer-${getRandomString(7)}`;\n this.#peerConnection =\n config?.peerConnection ??\n new PeerConnection(peerIdentity, {\n ...config,\n iceServers:\n config?.iceServers\n ?.map((server) => {\n const urls = Array.isArray(server.urls) ? server.urls : [server.urls];\n\n return urls.map((url) => {\n if (server.username && server.credential) {\n const [protocol, rest] = url.split(/:(.*)/);\n return `${protocol}:${server.username}:${server.credential}@${rest}`;\n }\n return url;\n });\n })\n .flat() ?? [],\n });\n } catch (error) {\n if (!error || !error.message) throw new exceptions.NotFoundError('Unknown error');\n throw new exceptions.SyntaxError(error.message);\n }\n\n // forward peerConnection events\n this.#peerConnection.onStateChange(() => {\n this.dispatchEvent(new Event('connectionstatechange'));\n });\n\n this.#peerConnection.onIceStateChange(() => {\n this.dispatchEvent(new Event('iceconnectionstatechange'));\n });\n\n this.#peerConnection.onSignalingStateChange(() => {\n this.dispatchEvent(new Event('signalingstatechange'));\n });\n\n this.#peerConnection.onGatheringStateChange(() => {\n this.dispatchEvent(new Event('icegatheringstatechange'));\n });\n\n this.#peerConnection.onDataChannel((channel) => {\n const dc = new RTCDataChannel(channel);\n this.#dataChannels.add(dc);\n this.dispatchEvent(new RTCDataChannelEvent('datachannel', { channel: dc }));\n });\n\n this.#peerConnection.onLocalDescription((sdp, type) => {\n if (type === 'offer') {\n this.#localOffer.resolve(new RTCSessionDescription({ sdp, type }));\n }\n\n if (type === 'answer') {\n this.#localAnswer.resolve(new RTCSessionDescription({ sdp, type }));\n }\n });\n\n this.#peerConnection.onLocalCandidate((candidate, sdpMid) => {\n if (sdpMid === 'unspec') {\n this.#localAnswer.reject(new Error(`Invalid description type ${sdpMid}`));\n return;\n }\n\n this.#localCandidates.push(new RTCIceCandidate({ candidate, sdpMid }));\n this.dispatchEvent(new RTCPeerConnectionIceEvent(new RTCIceCandidate({ candidate, sdpMid })));\n });\n\n // forward events to properties\n this.addEventListener('connectionstatechange', (e) => {\n this.onconnectionstatechange?.(e);\n });\n this.addEventListener('signalingstatechange', (e) => {\n this.onsignalingstatechange?.(e);\n });\n this.addEventListener('iceconnectionstatechange', (e) => {\n this.oniceconnectionstatechange?.(e);\n });\n this.addEventListener('icegatheringstatechange', (e) => {\n this.onicegatheringstatechange?.(e);\n });\n this.addEventListener('datachannel', (e) => {\n this.ondatachannel?.(e as RTCDataChannelEvent);\n });\n this.addEventListener('icecandidate', (e) => {\n this.onicecandidate?.(e as globalThis.RTCPeerConnectionIceEvent);\n });\n this.addEventListener('track', (e) => {\n this.ontrack?.(e as RTCTrackEvent);\n });\n this.addEventListener('negotiationneeded', (e) => {\n this.#announceNegotiation = true;\n this.onnegotiationneeded?.(e);\n });\n\n this.#sctp = new RTCSctpTransport({\n pc: this,\n });\n }\n\n // Extra FUnctions\n get ext_maxDataChannelId(): number {\n return this.#peerConnection.maxDataChannelId();\n }\n\n get ext_maxMessageSize(): number {\n return this.#peerConnection.maxMessageSize();\n }\n\n get ext_localCandidates(): globalThis.RTCIceCandidate[] {\n return this.#localCandidates;\n }\n\n get ext_remoteCandidates(): globalThis.RTCIceCandidate[] {\n return this.#remoteCandidates;\n }\n\n selectedCandidatePair(): {\n local: SelectedCandidateInfo;\n remote: SelectedCandidateInfo;\n } | null {\n return this.#peerConnection.getSelectedCandidatePair();\n }\n\n get canTrickleIceCandidates(): boolean | null {\n return this.#canTrickleIceCandidates;\n }\n\n get connectionState(): globalThis.RTCPeerConnectionState {\n return this.#peerConnection.state();\n }\n\n get iceConnectionState(): globalThis.RTCIceConnectionState {\n let state = this.#peerConnection.iceState();\n // libdatachannel uses 'completed' instead of 'connected'\n // see /webrtc/getstats.html\n if (state == 'completed') state = 'connected';\n return state;\n }\n\n get iceGatheringState(): globalThis.RTCIceGatheringState {\n return this.#peerConnection.gatheringState();\n }\n\n get currentLocalDescription(): globalThis.RTCSessionDescription {\n return new RTCSessionDescription(this.#peerConnection.localDescription() as any);\n }\n\n get currentRemoteDescription(): globalThis.RTCSessionDescription {\n return new RTCSessionDescription(this.#peerConnection.remoteDescription() as any);\n }\n\n get localDescription(): globalThis.RTCSessionDescription {\n return new RTCSessionDescription(this.#peerConnection.localDescription() as any);\n }\n\n get pendingLocalDescription(): globalThis.RTCSessionDescription {\n return new RTCSessionDescription(this.#peerConnection.localDescription() as any);\n }\n\n get pendingRemoteDescription(): globalThis.RTCSessionDescription {\n return new RTCSessionDescription(this.#peerConnection.remoteDescription() as any);\n }\n\n get remoteDescription(): globalThis.RTCSessionDescription {\n return new RTCSessionDescription(this.#peerConnection.remoteDescription() as any);\n }\n\n get sctp(): globalThis.RTCSctpTransport {\n return this.#sctp;\n }\n\n get signalingState(): globalThis.RTCSignalingState {\n return this.#peerConnection.signalingState();\n }\n\n async addIceCandidate(candidate?: globalThis.RTCIceCandidateInit | null): Promise<void> {\n if (!candidate || !candidate.candidate) {\n return;\n }\n\n if (candidate.sdpMid === null && candidate.sdpMLineIndex === null) {\n throw new TypeError('sdpMid must be set');\n }\n\n if (candidate.sdpMid === undefined && candidate.sdpMLineIndex == undefined) {\n throw new TypeError('sdpMid must be set');\n }\n\n // Reject if sdpMid format is not valid\n // ??\n if (candidate.sdpMid && candidate.sdpMid.length > 3) {\n // console.log(candidate.sdpMid);\n throw new exceptions.OperationError('Invalid sdpMid format');\n }\n\n // We don't care about sdpMLineIndex, just for test\n if (!candidate.sdpMid && candidate.sdpMLineIndex > 1) {\n throw new exceptions.OperationError('This is only for test case.');\n }\n\n try {\n this.#peerConnection.addRemoteCandidate(candidate.candidate, candidate.sdpMid ?? '0');\n this.#remoteCandidates.push(\n new RTCIceCandidate({\n candidate: candidate.candidate,\n sdpMid: candidate.sdpMid ?? '0',\n }),\n );\n } catch (error) {\n if (!error || !error.message) throw new exceptions.NotFoundError('Unknown error');\n\n // Check error Message if contains specific message\n if (error.message.includes('remote candidate without remote description'))\n throw new exceptions.InvalidStateError(error.message);\n if (error.message.includes('Invalid candidate format'))\n throw new exceptions.OperationError(error.message);\n\n throw new exceptions.NotFoundError(error.message);\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n addTrack(_track, ..._streams): globalThis.RTCRtpSender {\n throw new DOMException('Not implemented');\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n addTransceiver(_trackOrKind, _init): globalThis.RTCRtpTransceiver {\n throw new DOMException('Not implemented');\n }\n\n close(): void {\n this.#peerConnection.close();\n }\n\n createAnswer(): Promise<globalThis.RTCSessionDescriptionInit | any> {\n return this.#localAnswer;\n }\n\n createDataChannel(label: string, opts: globalThis.RTCDataChannelInit = {}): RTCDataChannel {\n const nativeOpts: DataChannelInitConfig = {\n ...opts,\n // libdatachannel uses \"unordered\", opposite of RTCDataChannelInit.ordered\n unordered: opts.ordered === false ? true : false,\n };\n\n const channel = this.#peerConnection.createDataChannel(label, nativeOpts);\n const dataChannel = new RTCDataChannel(channel, opts);\n\n // ensure we can close all channels when shutting down\n this.#dataChannels.add(dataChannel);\n dataChannel.addEventListener('close', () => {\n this.#dataChannels.delete(dataChannel);\n this.#dataChannelsClosed++;\n });\n\n if (this.#announceNegotiation == null) {\n this.#announceNegotiation = false;\n this.dispatchEvent(new Event('negotiationneeded'));\n }\n\n return dataChannel;\n }\n\n createOffer(): Promise<globalThis.RTCSessionDescriptionInit | any> {\n return this.#localOffer;\n }\n\n getConfiguration(): globalThis.RTCConfiguration {\n return this.#config;\n }\n\n getReceivers(): globalThis.RTCRtpReceiver[] {\n throw new DOMException('Not implemented');\n }\n\n getSenders(): globalThis.RTCRtpSender[] {\n throw new DOMException('Not implemented');\n }\n\n getStats(): Promise<globalThis.RTCStatsReport> | any {\n return new Promise((resolve) => {\n const report = new Map();\n const cp = this.#peerConnection?.getSelectedCandidatePair();\n const bytesSent = this.#peerConnection?.bytesSent();\n const bytesReceived = this.#peerConnection?.bytesReceived();\n const rtt = this.#peerConnection?.rtt();\n\n if (!cp) {\n return resolve(report);\n }\n\n const localIdRs = getRandomString(8);\n const localId = 'RTCIceCandidate_' + localIdRs;\n report.set(localId, {\n id: localId,\n type: 'local-candidate',\n timestamp: Date.now(),\n candidateType: cp.local.type,\n ip: cp.local.address,\n port: cp.local.port,\n });\n\n const remoteIdRs = getRandomString(8);\n const remoteId = 'RTCIceCandidate_' + remoteIdRs;\n report.set(remoteId, {\n id: remoteId,\n type: 'remote-candidate',\n timestamp: Date.now(),\n candidateType: cp.remote.type,\n ip: cp.remote.address,\n port: cp.remote.port,\n });\n\n const candidateId = 'RTCIceCandidatePair_' + localIdRs + '_' + remoteIdRs;\n report.set(candidateId, {\n id: candidateId,\n type: 'candidate-pair',\n timestamp: Date.now(),\n localCandidateId: localId,\n remoteCandidateId: remoteId,\n state: 'succeeded',\n nominated: true,\n writable: true,\n bytesSent: bytesSent,\n bytesReceived: bytesReceived,\n totalRoundTripTime: rtt,\n currentRoundTripTime: rtt,\n });\n\n const transportId = 'RTCTransport_0_1';\n report.set(transportId, {\n id: transportId,\n timestamp: Date.now(),\n type: 'transport',\n bytesSent: bytesSent,\n bytesReceived: bytesReceived,\n dtlsState: 'connected',\n selectedCandidatePairId: candidateId,\n selectedCandidatePairChanges: 1,\n });\n\n // peer-connection'\n report.set('P', {\n id: 'P',\n type: 'peer-connection',\n timestamp: Date.now(),\n dataChannelsOpened: this.#dataChannels.size,\n dataChannelsClosed: this.#dataChannelsClosed,\n });\n\n return resolve(report);\n });\n }\n\n getTransceivers(): globalThis.RTCRtpTransceiver[] {\n return []; // throw new DOMException('Not implemented');\n }\n\n removeTrack(): void {\n throw new DOMException('Not implemented');\n }\n\n restartIce(): Promise<void> {\n throw new DOMException('Not implemented');\n }\n\n async setLocalDescription(description: globalThis.RTCSessionDescriptionInit): Promise<void> {\n if (description?.type !== 'offer') {\n // any other type causes libdatachannel to throw\n return;\n }\n\n this.#peerConnection.setLocalDescription(description?.type as any);\n }\n\n async setRemoteDescription(description: globalThis.RTCSessionDescriptionInit): Promise<void> {\n if (description.sdp == null) {\n throw new DOMException('Remote SDP must be set');\n }\n\n this.#peerConnection.setRemoteDescription(description.sdp, description.type as any);\n }\n}\n\nfunction createDeferredPromise(): any {\n let resolve: any, reject: any;\n\n const promise = new Promise(function (_resolve, _reject) {\n resolve = _resolve;\n reject = _reject;\n });\n\n (promise as any).resolve = resolve;\n (promise as any).reject = reject;\n return promise;\n}\n\nfunction getRandomString(length: number): string {\n return Math.random()\n .toString(36)\n .substring(2, 2 + length);\n}\n"],"names":["PeerConnection","exceptions.NotFoundError","exceptions.SyntaxError","RTCDataChannel","RTCDataChannelEvent","RTCSessionDescription","RTCIceCandidate","RTCPeerConnectionIceEvent","RTCSctpTransport","exceptions.InvalidAccessError","exceptions.OperationError","exceptions.InvalidStateError"],"mappings":"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"} |
@@ -286,3 +286,8 @@ import { PeerConnection } from '../lib/index.mjs'; | ||
| createDataChannel(label, opts = {}) { | ||
| const channel = __privateGet(this, _peerConnection).createDataChannel(label, opts); | ||
| const nativeOpts = { | ||
| ...opts, | ||
| // libdatachannel uses "unordered", opposite of RTCDataChannelInit.ordered | ||
| unordered: opts.ordered === false ? true : false | ||
| }; | ||
| const channel = __privateGet(this, _peerConnection).createDataChannel(label, nativeOpts); | ||
| const dataChannel = new RTCDataChannel(channel, opts); | ||
@@ -289,0 +294,0 @@ __privateGet(this, _dataChannels).add(dataChannel); |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"RTCPeerConnection.mjs","sources":["../../../src/polyfill/RTCPeerConnection.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { SelectedCandidateInfo } from '../lib/types';\nimport { PeerConnection } from '../lib/index';\nimport RTCSessionDescription from './RTCSessionDescription';\nimport RTCDataChannel from './RTCDataChannel';\nimport RTCIceCandidate from './RTCIceCandidate';\nimport { RTCDataChannelEvent, RTCPeerConnectionIceEvent } from './Events';\nimport RTCSctpTransport from './RTCSctpTransport';\nimport * as exceptions from './Exception';\nimport RTCCertificate from './RTCCertificate';\n\n// extend RTCConfiguration with peerIdentity\ninterface RTCConfiguration extends globalThis.RTCConfiguration {\n peerIdentity?: string;\n peerConnection?: PeerConnection;\n}\n\nexport default class RTCPeerConnection extends EventTarget implements globalThis.RTCPeerConnection {\n static async generateCertificate(): Promise<RTCCertificate> {\n throw new DOMException('Not implemented');\n }\n\n #peerConnection: PeerConnection;\n #localOffer: ReturnType<typeof createDeferredPromise>;\n #localAnswer: ReturnType<typeof createDeferredPromise>;\n #dataChannels: Set<globalThis.RTCDataChannel>;\n #dataChannelsClosed = 0;\n #config: globalThis.RTCConfiguration;\n #canTrickleIceCandidates: boolean | null = null;\n #sctp: globalThis.RTCSctpTransport;\n #announceNegotiation = false;\n\n #localCandidates: globalThis.RTCIceCandidate[] = [];\n #remoteCandidates: globalThis.RTCIceCandidate[] = [];\n\n // events\n onconnectionstatechange: globalThis.RTCPeerConnection['onconnectionstatechange'] = null;\n // For ondatachannel we need to define type manually\n ondatachannel: ((this: globalThis.RTCPeerConnection, ev: RTCDataChannelEvent) => any) | null;\n onicecandidate: globalThis.RTCPeerConnection['onicecandidate'] = null;\n onicecandidateerror: globalThis.RTCPeerConnection['onicecandidateerror'] = null;\n oniceconnectionstatechange: globalThis.RTCPeerConnection['oniceconnectionstatechange'] = null;\n onicegatheringstatechange: globalThis.RTCPeerConnection['onicegatheringstatechange'] = null;\n onnegotiationneeded: globalThis.RTCPeerConnection['onnegotiationneeded'] = null;\n onsignalingstatechange: globalThis.RTCPeerConnection['onsignalingstatechange'] = null;\n ontrack: globalThis.RTCPeerConnection['ontrack'] = null;\n\n private _checkConfiguration(config: globalThis.RTCConfiguration): void {\n if (config && config.iceServers === undefined) config.iceServers = [];\n if (config && config.iceTransportPolicy === undefined) config.iceTransportPolicy = 'all';\n\n if (config?.iceServers === null) throw new TypeError('IceServers cannot be null');\n\n // Check for all the properties of iceServers\n if (Array.isArray(config?.iceServers)) {\n for (let i = 0; i < config.iceServers.length; i++) {\n if (config.iceServers[i] === null) throw new TypeError('IceServers cannot be null');\n if (config.iceServers[i] === undefined)\n throw new TypeError('IceServers cannot be undefined');\n if (Object.keys(config.iceServers[i]).length === 0)\n throw new TypeError('IceServers cannot be empty');\n\n // If iceServers is string convert to array\n if (typeof config.iceServers[i].urls === 'string')\n config.iceServers[i].urls = [config.iceServers[i].urls as string];\n\n // urls can not be empty\n if ((config.iceServers[i].urls as string[])?.some((url) => url == ''))\n throw new exceptions.SyntaxError('IceServers urls cannot be empty');\n\n // urls should be valid URLs and match the protocols \"stun:|turn:|turns:\"\n if (\n (config.iceServers[i].urls as string[])?.some((url) => {\n try {\n const parsedURL = new URL(url);\n\n return !/^(stun:|turn:|turns:)$/.test(parsedURL.protocol);\n } catch (error) {\n return true;\n }\n })\n )\n throw new exceptions.SyntaxError('IceServers urls wrong format');\n\n // If this is a turn server check for username and credential\n if ((config.iceServers[i].urls as string[])?.some((url) => url.startsWith('turn'))) {\n if (!config.iceServers[i].username)\n throw new exceptions.InvalidAccessError('IceServers username cannot be null');\n if (!config.iceServers[i].credential)\n throw new exceptions.InvalidAccessError('IceServers username cannot be undefined');\n }\n\n // length of urls can not be 0\n if (config.iceServers[i].urls?.length === 0)\n throw new exceptions.SyntaxError('IceServers urls cannot be empty');\n }\n }\n\n if (\n config &&\n config.iceTransportPolicy &&\n config.iceTransportPolicy !== 'all' &&\n config.iceTransportPolicy !== 'relay'\n )\n throw new TypeError('IceTransportPolicy must be either \"all\" or \"relay\"');\n }\n\n setConfiguration(config: globalThis.RTCConfiguration): void {\n this._checkConfiguration(config);\n this.#config = config;\n }\n\n constructor(config: RTCConfiguration = { iceServers: [], iceTransportPolicy: 'all' }) {\n super();\n\n this._checkConfiguration(config);\n this.#config = config;\n this.#localOffer = createDeferredPromise();\n this.#localAnswer = createDeferredPromise();\n this.#dataChannels = new Set();\n this.#canTrickleIceCandidates = null;\n\n try {\n const peerIdentity = (config as any)?.peerIdentity ?? `peer-${getRandomString(7)}`;\n this.#peerConnection =\n config?.peerConnection ??\n new PeerConnection(peerIdentity, {\n ...config,\n iceServers:\n config?.iceServers\n ?.map((server) => {\n const urls = Array.isArray(server.urls) ? server.urls : [server.urls];\n\n return urls.map((url) => {\n if (server.username && server.credential) {\n const [protocol, rest] = url.split(/:(.*)/);\n return `${protocol}:${server.username}:${server.credential}@${rest}`;\n }\n return url;\n });\n })\n .flat() ?? [],\n });\n } catch (error) {\n if (!error || !error.message) throw new exceptions.NotFoundError('Unknown error');\n throw new exceptions.SyntaxError(error.message);\n }\n\n // forward peerConnection events\n this.#peerConnection.onStateChange(() => {\n this.dispatchEvent(new Event('connectionstatechange'));\n });\n\n this.#peerConnection.onIceStateChange(() => {\n this.dispatchEvent(new Event('iceconnectionstatechange'));\n });\n\n this.#peerConnection.onSignalingStateChange(() => {\n this.dispatchEvent(new Event('signalingstatechange'));\n });\n\n this.#peerConnection.onGatheringStateChange(() => {\n this.dispatchEvent(new Event('icegatheringstatechange'));\n });\n\n this.#peerConnection.onDataChannel((channel) => {\n const dc = new RTCDataChannel(channel);\n this.#dataChannels.add(dc);\n this.dispatchEvent(new RTCDataChannelEvent('datachannel', { channel: dc }));\n });\n\n this.#peerConnection.onLocalDescription((sdp, type) => {\n if (type === 'offer') {\n this.#localOffer.resolve(new RTCSessionDescription({ sdp, type }));\n }\n\n if (type === 'answer') {\n this.#localAnswer.resolve(new RTCSessionDescription({ sdp, type }));\n }\n });\n\n this.#peerConnection.onLocalCandidate((candidate, sdpMid) => {\n if (sdpMid === 'unspec') {\n this.#localAnswer.reject(new Error(`Invalid description type ${sdpMid}`));\n return;\n }\n\n this.#localCandidates.push(new RTCIceCandidate({ candidate, sdpMid }));\n this.dispatchEvent(new RTCPeerConnectionIceEvent(new RTCIceCandidate({ candidate, sdpMid })));\n });\n\n // forward events to properties\n this.addEventListener('connectionstatechange', (e) => {\n this.onconnectionstatechange?.(e);\n });\n this.addEventListener('signalingstatechange', (e) => {\n this.onsignalingstatechange?.(e);\n });\n this.addEventListener('iceconnectionstatechange', (e) => {\n this.oniceconnectionstatechange?.(e);\n });\n this.addEventListener('icegatheringstatechange', (e) => {\n this.onicegatheringstatechange?.(e);\n });\n this.addEventListener('datachannel', (e) => {\n this.ondatachannel?.(e as RTCDataChannelEvent);\n });\n this.addEventListener('icecandidate', (e) => {\n this.onicecandidate?.(e as globalThis.RTCPeerConnectionIceEvent);\n });\n this.addEventListener('track', (e) => {\n this.ontrack?.(e as RTCTrackEvent);\n });\n this.addEventListener('negotiationneeded', (e) => {\n this.#announceNegotiation = true;\n this.onnegotiationneeded?.(e);\n });\n\n this.#sctp = new RTCSctpTransport({\n pc: this,\n });\n }\n\n // Extra FUnctions\n get ext_maxDataChannelId(): number {\n return this.#peerConnection.maxDataChannelId();\n }\n\n get ext_maxMessageSize(): number {\n return this.#peerConnection.maxMessageSize();\n }\n\n get ext_localCandidates(): globalThis.RTCIceCandidate[] {\n return this.#localCandidates;\n }\n\n get ext_remoteCandidates(): globalThis.RTCIceCandidate[] {\n return this.#remoteCandidates;\n }\n\n selectedCandidatePair(): {\n local: SelectedCandidateInfo;\n remote: SelectedCandidateInfo;\n } | null {\n return this.#peerConnection.getSelectedCandidatePair();\n }\n\n get canTrickleIceCandidates(): boolean | null {\n return this.#canTrickleIceCandidates;\n }\n\n get connectionState(): globalThis.RTCPeerConnectionState {\n return this.#peerConnection.state();\n }\n\n get iceConnectionState(): globalThis.RTCIceConnectionState {\n let state = this.#peerConnection.iceState();\n // libdatachannel uses 'completed' instead of 'connected'\n // see /webrtc/getstats.html\n if (state == 'completed') state = 'connected';\n return state;\n }\n\n get iceGatheringState(): globalThis.RTCIceGatheringState {\n return this.#peerConnection.gatheringState();\n }\n\n get currentLocalDescription(): globalThis.RTCSessionDescription {\n return new RTCSessionDescription(this.#peerConnection.localDescription() as any);\n }\n\n get currentRemoteDescription(): globalThis.RTCSessionDescription {\n return new RTCSessionDescription(this.#peerConnection.remoteDescription() as any);\n }\n\n get localDescription(): globalThis.RTCSessionDescription {\n return new RTCSessionDescription(this.#peerConnection.localDescription() as any);\n }\n\n get pendingLocalDescription(): globalThis.RTCSessionDescription {\n return new RTCSessionDescription(this.#peerConnection.localDescription() as any);\n }\n\n get pendingRemoteDescription(): globalThis.RTCSessionDescription {\n return new RTCSessionDescription(this.#peerConnection.remoteDescription() as any);\n }\n\n get remoteDescription(): globalThis.RTCSessionDescription {\n return new RTCSessionDescription(this.#peerConnection.remoteDescription() as any);\n }\n\n get sctp(): globalThis.RTCSctpTransport {\n return this.#sctp;\n }\n\n get signalingState(): globalThis.RTCSignalingState {\n return this.#peerConnection.signalingState();\n }\n\n async addIceCandidate(candidate?: globalThis.RTCIceCandidateInit | null): Promise<void> {\n if (!candidate || !candidate.candidate) {\n return;\n }\n\n if (candidate.sdpMid === null && candidate.sdpMLineIndex === null) {\n throw new TypeError('sdpMid must be set');\n }\n\n if (candidate.sdpMid === undefined && candidate.sdpMLineIndex == undefined) {\n throw new TypeError('sdpMid must be set');\n }\n\n // Reject if sdpMid format is not valid\n // ??\n if (candidate.sdpMid && candidate.sdpMid.length > 3) {\n // console.log(candidate.sdpMid);\n throw new exceptions.OperationError('Invalid sdpMid format');\n }\n\n // We don't care about sdpMLineIndex, just for test\n if (!candidate.sdpMid && candidate.sdpMLineIndex > 1) {\n throw new exceptions.OperationError('This is only for test case.');\n }\n\n try {\n this.#peerConnection.addRemoteCandidate(candidate.candidate, candidate.sdpMid ?? '0');\n this.#remoteCandidates.push(\n new RTCIceCandidate({\n candidate: candidate.candidate,\n sdpMid: candidate.sdpMid ?? '0',\n }),\n );\n } catch (error) {\n if (!error || !error.message) throw new exceptions.NotFoundError('Unknown error');\n\n // Check error Message if contains specific message\n if (error.message.includes('remote candidate without remote description'))\n throw new exceptions.InvalidStateError(error.message);\n if (error.message.includes('Invalid candidate format'))\n throw new exceptions.OperationError(error.message);\n\n throw new exceptions.NotFoundError(error.message);\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n addTrack(_track, ..._streams): globalThis.RTCRtpSender {\n throw new DOMException('Not implemented');\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n addTransceiver(_trackOrKind, _init): globalThis.RTCRtpTransceiver {\n throw new DOMException('Not implemented');\n }\n\n close(): void {\n this.#peerConnection.close();\n }\n\n createAnswer(): Promise<globalThis.RTCSessionDescriptionInit | any> {\n return this.#localAnswer;\n }\n\n createDataChannel(label: string, opts: globalThis.RTCDataChannelInit = {}): RTCDataChannel {\n const channel = this.#peerConnection.createDataChannel(label, opts);\n const dataChannel = new RTCDataChannel(channel, opts);\n\n // ensure we can close all channels when shutting down\n this.#dataChannels.add(dataChannel);\n dataChannel.addEventListener('close', () => {\n this.#dataChannels.delete(dataChannel);\n this.#dataChannelsClosed++;\n });\n\n if (this.#announceNegotiation == null) {\n this.#announceNegotiation = false;\n this.dispatchEvent(new Event('negotiationneeded'));\n }\n\n return dataChannel;\n }\n\n createOffer(): Promise<globalThis.RTCSessionDescriptionInit | any> {\n return this.#localOffer;\n }\n\n getConfiguration(): globalThis.RTCConfiguration {\n return this.#config;\n }\n\n getReceivers(): globalThis.RTCRtpReceiver[] {\n throw new DOMException('Not implemented');\n }\n\n getSenders(): globalThis.RTCRtpSender[] {\n throw new DOMException('Not implemented');\n }\n\n getStats(): Promise<globalThis.RTCStatsReport> | any {\n return new Promise((resolve) => {\n const report = new Map();\n const cp = this.#peerConnection?.getSelectedCandidatePair();\n const bytesSent = this.#peerConnection?.bytesSent();\n const bytesReceived = this.#peerConnection?.bytesReceived();\n const rtt = this.#peerConnection?.rtt();\n\n if (!cp) {\n return resolve(report);\n }\n\n const localIdRs = getRandomString(8);\n const localId = 'RTCIceCandidate_' + localIdRs;\n report.set(localId, {\n id: localId,\n type: 'local-candidate',\n timestamp: Date.now(),\n candidateType: cp.local.type,\n ip: cp.local.address,\n port: cp.local.port,\n });\n\n const remoteIdRs = getRandomString(8);\n const remoteId = 'RTCIceCandidate_' + remoteIdRs;\n report.set(remoteId, {\n id: remoteId,\n type: 'remote-candidate',\n timestamp: Date.now(),\n candidateType: cp.remote.type,\n ip: cp.remote.address,\n port: cp.remote.port,\n });\n\n const candidateId = 'RTCIceCandidatePair_' + localIdRs + '_' + remoteIdRs;\n report.set(candidateId, {\n id: candidateId,\n type: 'candidate-pair',\n timestamp: Date.now(),\n localCandidateId: localId,\n remoteCandidateId: remoteId,\n state: 'succeeded',\n nominated: true,\n writable: true,\n bytesSent: bytesSent,\n bytesReceived: bytesReceived,\n totalRoundTripTime: rtt,\n currentRoundTripTime: rtt,\n });\n\n const transportId = 'RTCTransport_0_1';\n report.set(transportId, {\n id: transportId,\n timestamp: Date.now(),\n type: 'transport',\n bytesSent: bytesSent,\n bytesReceived: bytesReceived,\n dtlsState: 'connected',\n selectedCandidatePairId: candidateId,\n selectedCandidatePairChanges: 1,\n });\n\n // peer-connection'\n report.set('P', {\n id: 'P',\n type: 'peer-connection',\n timestamp: Date.now(),\n dataChannelsOpened: this.#dataChannels.size,\n dataChannelsClosed: this.#dataChannelsClosed,\n });\n\n return resolve(report);\n });\n }\n\n getTransceivers(): globalThis.RTCRtpTransceiver[] {\n return []; // throw new DOMException('Not implemented');\n }\n\n removeTrack(): void {\n throw new DOMException('Not implemented');\n }\n\n restartIce(): Promise<void> {\n throw new DOMException('Not implemented');\n }\n\n async setLocalDescription(description: globalThis.RTCSessionDescriptionInit): Promise<void> {\n if (description?.type !== 'offer') {\n // any other type causes libdatachannel to throw\n return;\n }\n\n this.#peerConnection.setLocalDescription(description?.type as any);\n }\n\n async setRemoteDescription(description: globalThis.RTCSessionDescriptionInit): Promise<void> {\n if (description.sdp == null) {\n throw new DOMException('Remote SDP must be set');\n }\n\n this.#peerConnection.setRemoteDescription(description.sdp, description.type as any);\n }\n}\n\nfunction createDeferredPromise(): any {\n let resolve: any, reject: any;\n\n const promise = new Promise(function (_resolve, _reject) {\n resolve = _resolve;\n reject = _reject;\n });\n\n (promise as any).resolve = resolve;\n (promise as any).reject = reject;\n return promise;\n}\n\nfunction getRandomString(length: number): string {\n return Math.random()\n .toString(36)\n .substring(2, 2 + length);\n}\n"],"names":["exceptions.NotFoundError","exceptions.SyntaxError","exceptions.InvalidAccessError","exceptions.OperationError","exceptions.InvalidStateError"],"mappings":"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"} | ||
| {"version":3,"file":"RTCPeerConnection.mjs","sources":["../../../src/polyfill/RTCPeerConnection.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { DataChannelInitConfig, SelectedCandidateInfo } from '../lib/types';\nimport { PeerConnection } from '../lib/index';\nimport RTCSessionDescription from './RTCSessionDescription';\nimport RTCDataChannel from './RTCDataChannel';\nimport RTCIceCandidate from './RTCIceCandidate';\nimport { RTCDataChannelEvent, RTCPeerConnectionIceEvent } from './Events';\nimport RTCSctpTransport from './RTCSctpTransport';\nimport * as exceptions from './Exception';\nimport RTCCertificate from './RTCCertificate';\n\n// extend RTCConfiguration with peerIdentity\ninterface RTCConfiguration extends globalThis.RTCConfiguration {\n peerIdentity?: string;\n peerConnection?: PeerConnection;\n}\n\nexport default class RTCPeerConnection extends EventTarget implements globalThis.RTCPeerConnection {\n static async generateCertificate(): Promise<RTCCertificate> {\n throw new DOMException('Not implemented');\n }\n\n #peerConnection: PeerConnection;\n #localOffer: ReturnType<typeof createDeferredPromise>;\n #localAnswer: ReturnType<typeof createDeferredPromise>;\n #dataChannels: Set<globalThis.RTCDataChannel>;\n #dataChannelsClosed = 0;\n #config: globalThis.RTCConfiguration;\n #canTrickleIceCandidates: boolean | null = null;\n #sctp: globalThis.RTCSctpTransport;\n #announceNegotiation = false;\n\n #localCandidates: globalThis.RTCIceCandidate[] = [];\n #remoteCandidates: globalThis.RTCIceCandidate[] = [];\n\n // events\n onconnectionstatechange: globalThis.RTCPeerConnection['onconnectionstatechange'] = null;\n // For ondatachannel we need to define type manually\n ondatachannel: ((this: globalThis.RTCPeerConnection, ev: globalThis.RTCDataChannelEvent) => any) | null;\n onicecandidate: globalThis.RTCPeerConnection['onicecandidate'] = null;\n onicecandidateerror: globalThis.RTCPeerConnection['onicecandidateerror'] = null;\n oniceconnectionstatechange: globalThis.RTCPeerConnection['oniceconnectionstatechange'] = null;\n onicegatheringstatechange: globalThis.RTCPeerConnection['onicegatheringstatechange'] = null;\n onnegotiationneeded: globalThis.RTCPeerConnection['onnegotiationneeded'] = null;\n onsignalingstatechange: globalThis.RTCPeerConnection['onsignalingstatechange'] = null;\n ontrack: globalThis.RTCPeerConnection['ontrack'] = null;\n\n private _checkConfiguration(config: globalThis.RTCConfiguration): void {\n if (config && config.iceServers === undefined) config.iceServers = [];\n if (config && config.iceTransportPolicy === undefined) config.iceTransportPolicy = 'all';\n\n if (config?.iceServers === null) throw new TypeError('IceServers cannot be null');\n\n // Check for all the properties of iceServers\n if (Array.isArray(config?.iceServers)) {\n for (let i = 0; i < config.iceServers.length; i++) {\n if (config.iceServers[i] === null) throw new TypeError('IceServers cannot be null');\n if (config.iceServers[i] === undefined)\n throw new TypeError('IceServers cannot be undefined');\n if (Object.keys(config.iceServers[i]).length === 0)\n throw new TypeError('IceServers cannot be empty');\n\n // If iceServers is string convert to array\n if (typeof config.iceServers[i].urls === 'string')\n config.iceServers[i].urls = [config.iceServers[i].urls as string];\n\n // urls can not be empty\n if ((config.iceServers[i].urls as string[])?.some((url) => url == ''))\n throw new exceptions.SyntaxError('IceServers urls cannot be empty');\n\n // urls should be valid URLs and match the protocols \"stun:|turn:|turns:\"\n if (\n (config.iceServers[i].urls as string[])?.some((url) => {\n try {\n const parsedURL = new URL(url);\n\n return !/^(stun:|turn:|turns:)$/.test(parsedURL.protocol);\n } catch (error) {\n return true;\n }\n })\n )\n throw new exceptions.SyntaxError('IceServers urls wrong format');\n\n // If this is a turn server check for username and credential\n if ((config.iceServers[i].urls as string[])?.some((url) => url.startsWith('turn'))) {\n if (!config.iceServers[i].username)\n throw new exceptions.InvalidAccessError('IceServers username cannot be null');\n if (!config.iceServers[i].credential)\n throw new exceptions.InvalidAccessError('IceServers username cannot be undefined');\n }\n\n // length of urls can not be 0\n if (config.iceServers[i].urls?.length === 0)\n throw new exceptions.SyntaxError('IceServers urls cannot be empty');\n }\n }\n\n if (\n config &&\n config.iceTransportPolicy &&\n config.iceTransportPolicy !== 'all' &&\n config.iceTransportPolicy !== 'relay'\n )\n throw new TypeError('IceTransportPolicy must be either \"all\" or \"relay\"');\n }\n\n setConfiguration(config: globalThis.RTCConfiguration): void {\n this._checkConfiguration(config);\n this.#config = config;\n }\n\n constructor(config: RTCConfiguration = { iceServers: [], iceTransportPolicy: 'all' }) {\n super();\n\n this._checkConfiguration(config);\n this.#config = config;\n this.#localOffer = createDeferredPromise();\n this.#localAnswer = createDeferredPromise();\n this.#dataChannels = new Set();\n this.#canTrickleIceCandidates = null;\n\n try {\n const peerIdentity = (config as any)?.peerIdentity ?? `peer-${getRandomString(7)}`;\n this.#peerConnection =\n config?.peerConnection ??\n new PeerConnection(peerIdentity, {\n ...config,\n iceServers:\n config?.iceServers\n ?.map((server) => {\n const urls = Array.isArray(server.urls) ? server.urls : [server.urls];\n\n return urls.map((url) => {\n if (server.username && server.credential) {\n const [protocol, rest] = url.split(/:(.*)/);\n return `${protocol}:${server.username}:${server.credential}@${rest}`;\n }\n return url;\n });\n })\n .flat() ?? [],\n });\n } catch (error) {\n if (!error || !error.message) throw new exceptions.NotFoundError('Unknown error');\n throw new exceptions.SyntaxError(error.message);\n }\n\n // forward peerConnection events\n this.#peerConnection.onStateChange(() => {\n this.dispatchEvent(new Event('connectionstatechange'));\n });\n\n this.#peerConnection.onIceStateChange(() => {\n this.dispatchEvent(new Event('iceconnectionstatechange'));\n });\n\n this.#peerConnection.onSignalingStateChange(() => {\n this.dispatchEvent(new Event('signalingstatechange'));\n });\n\n this.#peerConnection.onGatheringStateChange(() => {\n this.dispatchEvent(new Event('icegatheringstatechange'));\n });\n\n this.#peerConnection.onDataChannel((channel) => {\n const dc = new RTCDataChannel(channel);\n this.#dataChannels.add(dc);\n this.dispatchEvent(new RTCDataChannelEvent('datachannel', { channel: dc }));\n });\n\n this.#peerConnection.onLocalDescription((sdp, type) => {\n if (type === 'offer') {\n this.#localOffer.resolve(new RTCSessionDescription({ sdp, type }));\n }\n\n if (type === 'answer') {\n this.#localAnswer.resolve(new RTCSessionDescription({ sdp, type }));\n }\n });\n\n this.#peerConnection.onLocalCandidate((candidate, sdpMid) => {\n if (sdpMid === 'unspec') {\n this.#localAnswer.reject(new Error(`Invalid description type ${sdpMid}`));\n return;\n }\n\n this.#localCandidates.push(new RTCIceCandidate({ candidate, sdpMid }));\n this.dispatchEvent(new RTCPeerConnectionIceEvent(new RTCIceCandidate({ candidate, sdpMid })));\n });\n\n // forward events to properties\n this.addEventListener('connectionstatechange', (e) => {\n this.onconnectionstatechange?.(e);\n });\n this.addEventListener('signalingstatechange', (e) => {\n this.onsignalingstatechange?.(e);\n });\n this.addEventListener('iceconnectionstatechange', (e) => {\n this.oniceconnectionstatechange?.(e);\n });\n this.addEventListener('icegatheringstatechange', (e) => {\n this.onicegatheringstatechange?.(e);\n });\n this.addEventListener('datachannel', (e) => {\n this.ondatachannel?.(e as RTCDataChannelEvent);\n });\n this.addEventListener('icecandidate', (e) => {\n this.onicecandidate?.(e as globalThis.RTCPeerConnectionIceEvent);\n });\n this.addEventListener('track', (e) => {\n this.ontrack?.(e as RTCTrackEvent);\n });\n this.addEventListener('negotiationneeded', (e) => {\n this.#announceNegotiation = true;\n this.onnegotiationneeded?.(e);\n });\n\n this.#sctp = new RTCSctpTransport({\n pc: this,\n });\n }\n\n // Extra FUnctions\n get ext_maxDataChannelId(): number {\n return this.#peerConnection.maxDataChannelId();\n }\n\n get ext_maxMessageSize(): number {\n return this.#peerConnection.maxMessageSize();\n }\n\n get ext_localCandidates(): globalThis.RTCIceCandidate[] {\n return this.#localCandidates;\n }\n\n get ext_remoteCandidates(): globalThis.RTCIceCandidate[] {\n return this.#remoteCandidates;\n }\n\n selectedCandidatePair(): {\n local: SelectedCandidateInfo;\n remote: SelectedCandidateInfo;\n } | null {\n return this.#peerConnection.getSelectedCandidatePair();\n }\n\n get canTrickleIceCandidates(): boolean | null {\n return this.#canTrickleIceCandidates;\n }\n\n get connectionState(): globalThis.RTCPeerConnectionState {\n return this.#peerConnection.state();\n }\n\n get iceConnectionState(): globalThis.RTCIceConnectionState {\n let state = this.#peerConnection.iceState();\n // libdatachannel uses 'completed' instead of 'connected'\n // see /webrtc/getstats.html\n if (state == 'completed') state = 'connected';\n return state;\n }\n\n get iceGatheringState(): globalThis.RTCIceGatheringState {\n return this.#peerConnection.gatheringState();\n }\n\n get currentLocalDescription(): globalThis.RTCSessionDescription {\n return new RTCSessionDescription(this.#peerConnection.localDescription() as any);\n }\n\n get currentRemoteDescription(): globalThis.RTCSessionDescription {\n return new RTCSessionDescription(this.#peerConnection.remoteDescription() as any);\n }\n\n get localDescription(): globalThis.RTCSessionDescription {\n return new RTCSessionDescription(this.#peerConnection.localDescription() as any);\n }\n\n get pendingLocalDescription(): globalThis.RTCSessionDescription {\n return new RTCSessionDescription(this.#peerConnection.localDescription() as any);\n }\n\n get pendingRemoteDescription(): globalThis.RTCSessionDescription {\n return new RTCSessionDescription(this.#peerConnection.remoteDescription() as any);\n }\n\n get remoteDescription(): globalThis.RTCSessionDescription {\n return new RTCSessionDescription(this.#peerConnection.remoteDescription() as any);\n }\n\n get sctp(): globalThis.RTCSctpTransport {\n return this.#sctp;\n }\n\n get signalingState(): globalThis.RTCSignalingState {\n return this.#peerConnection.signalingState();\n }\n\n async addIceCandidate(candidate?: globalThis.RTCIceCandidateInit | null): Promise<void> {\n if (!candidate || !candidate.candidate) {\n return;\n }\n\n if (candidate.sdpMid === null && candidate.sdpMLineIndex === null) {\n throw new TypeError('sdpMid must be set');\n }\n\n if (candidate.sdpMid === undefined && candidate.sdpMLineIndex == undefined) {\n throw new TypeError('sdpMid must be set');\n }\n\n // Reject if sdpMid format is not valid\n // ??\n if (candidate.sdpMid && candidate.sdpMid.length > 3) {\n // console.log(candidate.sdpMid);\n throw new exceptions.OperationError('Invalid sdpMid format');\n }\n\n // We don't care about sdpMLineIndex, just for test\n if (!candidate.sdpMid && candidate.sdpMLineIndex > 1) {\n throw new exceptions.OperationError('This is only for test case.');\n }\n\n try {\n this.#peerConnection.addRemoteCandidate(candidate.candidate, candidate.sdpMid ?? '0');\n this.#remoteCandidates.push(\n new RTCIceCandidate({\n candidate: candidate.candidate,\n sdpMid: candidate.sdpMid ?? '0',\n }),\n );\n } catch (error) {\n if (!error || !error.message) throw new exceptions.NotFoundError('Unknown error');\n\n // Check error Message if contains specific message\n if (error.message.includes('remote candidate without remote description'))\n throw new exceptions.InvalidStateError(error.message);\n if (error.message.includes('Invalid candidate format'))\n throw new exceptions.OperationError(error.message);\n\n throw new exceptions.NotFoundError(error.message);\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n addTrack(_track, ..._streams): globalThis.RTCRtpSender {\n throw new DOMException('Not implemented');\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n addTransceiver(_trackOrKind, _init): globalThis.RTCRtpTransceiver {\n throw new DOMException('Not implemented');\n }\n\n close(): void {\n this.#peerConnection.close();\n }\n\n createAnswer(): Promise<globalThis.RTCSessionDescriptionInit | any> {\n return this.#localAnswer;\n }\n\n createDataChannel(label: string, opts: globalThis.RTCDataChannelInit = {}): RTCDataChannel {\n const nativeOpts: DataChannelInitConfig = {\n ...opts,\n // libdatachannel uses \"unordered\", opposite of RTCDataChannelInit.ordered\n unordered: opts.ordered === false ? true : false,\n };\n\n const channel = this.#peerConnection.createDataChannel(label, nativeOpts);\n const dataChannel = new RTCDataChannel(channel, opts);\n\n // ensure we can close all channels when shutting down\n this.#dataChannels.add(dataChannel);\n dataChannel.addEventListener('close', () => {\n this.#dataChannels.delete(dataChannel);\n this.#dataChannelsClosed++;\n });\n\n if (this.#announceNegotiation == null) {\n this.#announceNegotiation = false;\n this.dispatchEvent(new Event('negotiationneeded'));\n }\n\n return dataChannel;\n }\n\n createOffer(): Promise<globalThis.RTCSessionDescriptionInit | any> {\n return this.#localOffer;\n }\n\n getConfiguration(): globalThis.RTCConfiguration {\n return this.#config;\n }\n\n getReceivers(): globalThis.RTCRtpReceiver[] {\n throw new DOMException('Not implemented');\n }\n\n getSenders(): globalThis.RTCRtpSender[] {\n throw new DOMException('Not implemented');\n }\n\n getStats(): Promise<globalThis.RTCStatsReport> | any {\n return new Promise((resolve) => {\n const report = new Map();\n const cp = this.#peerConnection?.getSelectedCandidatePair();\n const bytesSent = this.#peerConnection?.bytesSent();\n const bytesReceived = this.#peerConnection?.bytesReceived();\n const rtt = this.#peerConnection?.rtt();\n\n if (!cp) {\n return resolve(report);\n }\n\n const localIdRs = getRandomString(8);\n const localId = 'RTCIceCandidate_' + localIdRs;\n report.set(localId, {\n id: localId,\n type: 'local-candidate',\n timestamp: Date.now(),\n candidateType: cp.local.type,\n ip: cp.local.address,\n port: cp.local.port,\n });\n\n const remoteIdRs = getRandomString(8);\n const remoteId = 'RTCIceCandidate_' + remoteIdRs;\n report.set(remoteId, {\n id: remoteId,\n type: 'remote-candidate',\n timestamp: Date.now(),\n candidateType: cp.remote.type,\n ip: cp.remote.address,\n port: cp.remote.port,\n });\n\n const candidateId = 'RTCIceCandidatePair_' + localIdRs + '_' + remoteIdRs;\n report.set(candidateId, {\n id: candidateId,\n type: 'candidate-pair',\n timestamp: Date.now(),\n localCandidateId: localId,\n remoteCandidateId: remoteId,\n state: 'succeeded',\n nominated: true,\n writable: true,\n bytesSent: bytesSent,\n bytesReceived: bytesReceived,\n totalRoundTripTime: rtt,\n currentRoundTripTime: rtt,\n });\n\n const transportId = 'RTCTransport_0_1';\n report.set(transportId, {\n id: transportId,\n timestamp: Date.now(),\n type: 'transport',\n bytesSent: bytesSent,\n bytesReceived: bytesReceived,\n dtlsState: 'connected',\n selectedCandidatePairId: candidateId,\n selectedCandidatePairChanges: 1,\n });\n\n // peer-connection'\n report.set('P', {\n id: 'P',\n type: 'peer-connection',\n timestamp: Date.now(),\n dataChannelsOpened: this.#dataChannels.size,\n dataChannelsClosed: this.#dataChannelsClosed,\n });\n\n return resolve(report);\n });\n }\n\n getTransceivers(): globalThis.RTCRtpTransceiver[] {\n return []; // throw new DOMException('Not implemented');\n }\n\n removeTrack(): void {\n throw new DOMException('Not implemented');\n }\n\n restartIce(): Promise<void> {\n throw new DOMException('Not implemented');\n }\n\n async setLocalDescription(description: globalThis.RTCSessionDescriptionInit): Promise<void> {\n if (description?.type !== 'offer') {\n // any other type causes libdatachannel to throw\n return;\n }\n\n this.#peerConnection.setLocalDescription(description?.type as any);\n }\n\n async setRemoteDescription(description: globalThis.RTCSessionDescriptionInit): Promise<void> {\n if (description.sdp == null) {\n throw new DOMException('Remote SDP must be set');\n }\n\n this.#peerConnection.setRemoteDescription(description.sdp, description.type as any);\n }\n}\n\nfunction createDeferredPromise(): any {\n let resolve: any, reject: any;\n\n const promise = new Promise(function (_resolve, _reject) {\n resolve = _resolve;\n reject = _reject;\n });\n\n (promise as any).resolve = resolve;\n (promise as any).reject = reject;\n return promise;\n}\n\nfunction getRandomString(length: number): string {\n return Math.random()\n .toString(36)\n .substring(2, 2 + length);\n}\n"],"names":["exceptions.NotFoundError","exceptions.SyntaxError","exceptions.InvalidAccessError","exceptions.OperationError","exceptions.InvalidStateError"],"mappings":"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"} |
| import { SelectedCandidateInfo } from '../lib/types'; | ||
| import { PeerConnection } from '../lib/index'; | ||
| import RTCDataChannel from './RTCDataChannel.js'; | ||
| import { RTCDataChannelEvent } from './Events.js'; | ||
| import RTCCertificate from './RTCCertificate.js'; | ||
@@ -15,3 +14,3 @@ | ||
| onconnectionstatechange: globalThis.RTCPeerConnection['onconnectionstatechange']; | ||
| ondatachannel: ((this: globalThis.RTCPeerConnection, ev: RTCDataChannelEvent) => any) | null; | ||
| ondatachannel: ((this: globalThis.RTCPeerConnection, ev: globalThis.RTCDataChannelEvent) => any) | null; | ||
| onicecandidate: globalThis.RTCPeerConnection['onicecandidate']; | ||
@@ -18,0 +17,0 @@ onicecandidateerror: globalThis.RTCPeerConnection['onicecandidateerror']; |
+3
-2
| { | ||
| "name": "node-datachannel", | ||
| "version": "0.29.0", | ||
| "version": "0.30.0", | ||
| "description": "WebRTC For Node.js and Electron. libdatachannel node bindings.", | ||
@@ -54,3 +54,4 @@ "main": "./dist/cjs/lib/index.cjs", | ||
| "prepack": "npm run build:tsc", | ||
| "prettier": "prettier --write ." | ||
| "prettier": "prettier --write .", | ||
| "format-cpp": "find src/cpp \\( -name \"*.h\" -o -name \"*.cpp\" \\) -exec clang-format -i \"{}\" \\; " | ||
| }, | ||
@@ -57,0 +58,0 @@ "binary": { |
+385
-364
| #include "data-channel-wrapper.h" | ||
| #include "plog/Log.h" | ||
@@ -10,6 +9,6 @@ | ||
| { | ||
| PLOG_DEBUG << "CloseAll() called"; | ||
| auto copy(instances); | ||
| for (auto inst : copy) | ||
| inst->doClose(); | ||
| PLOG_DEBUG << "CloseAll() called"; | ||
| auto copy(instances); | ||
| for (auto inst : copy) | ||
| inst->doClose(); | ||
| } | ||
@@ -19,6 +18,6 @@ | ||
| { | ||
| PLOG_DEBUG << "CleanupAll() called"; | ||
| auto copy(instances); | ||
| for (auto inst : copy) | ||
| inst->doCleanup(); | ||
| PLOG_DEBUG << "CleanupAll() called"; | ||
| auto copy(instances); | ||
| for (auto inst : copy) | ||
| inst->doCleanup(); | ||
| } | ||
@@ -28,34 +27,33 @@ | ||
| { | ||
| Napi::HandleScope scope(env); | ||
| Napi::HandleScope scope(env); | ||
| Napi::Function func = DefineClass( | ||
| env, | ||
| "DataChannel", | ||
| { | ||
| InstanceMethod("close", &DataChannelWrapper::close), | ||
| InstanceMethod("getLabel", &DataChannelWrapper::getLabel), | ||
| InstanceMethod("getId", &DataChannelWrapper::getId), | ||
| InstanceMethod("getProtocol", &DataChannelWrapper::getProtocol), | ||
| InstanceMethod("sendMessage", &DataChannelWrapper::sendMessage), | ||
| InstanceMethod("sendMessageBinary", &DataChannelWrapper::sendMessageBinary), | ||
| InstanceMethod("isOpen", &DataChannelWrapper::isOpen), | ||
| InstanceMethod("bufferedAmount", &DataChannelWrapper::bufferedAmount), | ||
| InstanceMethod("maxMessageSize", &DataChannelWrapper::maxMessageSize), | ||
| InstanceMethod("setBufferedAmountLowThreshold", &DataChannelWrapper::setBufferedAmountLowThreshold), | ||
| InstanceMethod("onOpen", &DataChannelWrapper::onOpen), | ||
| InstanceMethod("onClosed", &DataChannelWrapper::onClosed), | ||
| InstanceMethod("onError", &DataChannelWrapper::onError), | ||
| InstanceMethod("onBufferedAmountLow", &DataChannelWrapper::onBufferedAmountLow), | ||
| InstanceMethod("onMessage", &DataChannelWrapper::onMessage), | ||
| }); | ||
| Napi::Function func = DefineClass( | ||
| env, "DataChannel", | ||
| { | ||
| InstanceMethod("close", &DataChannelWrapper::close), | ||
| InstanceMethod("getLabel", &DataChannelWrapper::getLabel), | ||
| InstanceMethod("getId", &DataChannelWrapper::getId), | ||
| InstanceMethod("getProtocol", &DataChannelWrapper::getProtocol), | ||
| InstanceMethod("sendMessage", &DataChannelWrapper::sendMessage), | ||
| InstanceMethod("sendMessageBinary", &DataChannelWrapper::sendMessageBinary), | ||
| InstanceMethod("isOpen", &DataChannelWrapper::isOpen), | ||
| InstanceMethod("bufferedAmount", &DataChannelWrapper::bufferedAmount), | ||
| InstanceMethod("maxMessageSize", &DataChannelWrapper::maxMessageSize), | ||
| InstanceMethod("setBufferedAmountLowThreshold", &DataChannelWrapper::setBufferedAmountLowThreshold), | ||
| InstanceMethod("onOpen", &DataChannelWrapper::onOpen), | ||
| InstanceMethod("onClosed", &DataChannelWrapper::onClosed), | ||
| InstanceMethod("onError", &DataChannelWrapper::onError), | ||
| InstanceMethod("onBufferedAmountLow", &DataChannelWrapper::onBufferedAmountLow), | ||
| InstanceMethod("onMessage", &DataChannelWrapper::onMessage), | ||
| }); | ||
| // If this is not the first call, we don't want to reassign the constructor (hot-reload problem) | ||
| if(constructor.IsEmpty()) | ||
| { | ||
| constructor = Napi::Persistent(func); | ||
| constructor.SuppressDestruct(); | ||
| } | ||
| // If this is not the first call, we don't want to reassign the constructor (hot-reload problem) | ||
| if (constructor.IsEmpty()) | ||
| { | ||
| constructor = Napi::Persistent(func); | ||
| constructor.SuppressDestruct(); | ||
| } | ||
| exports.Set("DataChannel", func); | ||
| return exports; | ||
| exports.Set("DataChannel", func); | ||
| return exports; | ||
| } | ||
@@ -65,10 +63,11 @@ | ||
| { | ||
| PLOG_DEBUG << "Constructor called"; | ||
| mDataChannelPtr = *(info[0].As<Napi::External<std::shared_ptr<rtc::DataChannel>>>().Data()); | ||
| PLOG_DEBUG << "Data Channel created"; | ||
| PLOG_DEBUG << "Constructor called"; | ||
| mDataChannelPtr = *(info[0].As<Napi::External<std::shared_ptr<rtc::DataChannel>>>().Data()); | ||
| PLOG_DEBUG << "Data Channel created"; | ||
| // Closed callback must be set to trigger cleanup | ||
| mOnClosedCallback = std::make_unique<ThreadSafeCallback>(Napi::Function::New(info.Env(), [](const Napi::CallbackInfo&){})); | ||
| // Closed callback must be set to trigger cleanup | ||
| mOnClosedCallback = | ||
| std::make_unique<ThreadSafeCallback>(Napi::Function::New(info.Env(), [](const Napi::CallbackInfo &) {})); | ||
| instances.insert(this); | ||
| instances.insert(this); | ||
| } | ||
@@ -78,4 +77,4 @@ | ||
| { | ||
| PLOG_DEBUG << "Destructor called"; | ||
| doClose(); | ||
| PLOG_DEBUG << "Destructor called"; | ||
| doClose(); | ||
| } | ||
@@ -85,22 +84,22 @@ | ||
| { | ||
| PLOG_DEBUG << "doClose() called"; | ||
| if (mDataChannelPtr) | ||
| PLOG_DEBUG << "doClose() called"; | ||
| if (mDataChannelPtr) | ||
| { | ||
| PLOG_DEBUG << "Closing..."; | ||
| try | ||
| { | ||
| PLOG_DEBUG << "Closing..."; | ||
| try | ||
| { | ||
| mDataChannelPtr->close(); | ||
| mDataChannelPtr.reset(); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| std::cerr << std::string("libdatachannel error while closing data channel: ") + ex.what() << std::endl; | ||
| return; | ||
| } | ||
| mDataChannelPtr->close(); | ||
| mDataChannelPtr.reset(); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| std::cerr << std::string("libdatachannel error while closing data channel: ") + ex.what() << std::endl; | ||
| return; | ||
| } | ||
| } | ||
| mOnOpenCallback.reset(); | ||
| mOnErrorCallback.reset(); | ||
| mOnBufferedAmountLowCallback.reset(); | ||
| mOnMessageCallback.reset(); | ||
| mOnOpenCallback.reset(); | ||
| mOnErrorCallback.reset(); | ||
| mOnBufferedAmountLowCallback.reset(); | ||
| mOnMessageCallback.reset(); | ||
| } | ||
@@ -110,16 +109,16 @@ | ||
| { | ||
| PLOG_DEBUG << "doCleanup() called"; | ||
| mOnClosedCallback.reset(); | ||
| PLOG_DEBUG << "doCleanup() called"; | ||
| mOnClosedCallback.reset(); | ||
| // For close there are 3 cases: | ||
| // 1. Close was called from JS | ||
| // 2. Close was called from RTC | ||
| // 3. Close was not called from JS or RTC (we are destroying the object) | ||
| // This could be coming from rtc (case 2) | ||
| mDataChannelPtr.reset(); | ||
| mOnOpenCallback.reset(); | ||
| mOnErrorCallback.reset(); | ||
| mOnBufferedAmountLowCallback.reset(); | ||
| mOnMessageCallback.reset(); | ||
| instances.erase(this); | ||
| // For close there are 3 cases: | ||
| // 1. Close was called from JS | ||
| // 2. Close was called from RTC | ||
| // 3. Close was not called from JS or RTC (we are destroying the object) | ||
| // This could be coming from rtc (case 2) | ||
| mDataChannelPtr.reset(); | ||
| mOnOpenCallback.reset(); | ||
| mOnErrorCallback.reset(); | ||
| mOnBufferedAmountLowCallback.reset(); | ||
| mOnMessageCallback.reset(); | ||
| instances.erase(this); | ||
| } | ||
@@ -129,4 +128,4 @@ | ||
| { | ||
| PLOG_DEBUG << "close() called"; | ||
| doClose(); | ||
| PLOG_DEBUG << "close() called"; | ||
| doClose(); | ||
| } | ||
@@ -136,10 +135,10 @@ | ||
| { | ||
| PLOG_DEBUG << "getLabel() called"; | ||
| if (!mDataChannelPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "getLabel() called on destroyed channel").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| PLOG_DEBUG << "getLabel() called"; | ||
| if (!mDataChannelPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "getLabel() called on destroyed channel").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| return Napi::String::New(info.Env(), mDataChannelPtr->label()); | ||
| return Napi::String::New(info.Env(), mDataChannelPtr->label()); | ||
| } | ||
@@ -149,10 +148,10 @@ | ||
| { | ||
| PLOG_DEBUG << "getId() called"; | ||
| if (!mDataChannelPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "getId() called on destroyed channel").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| PLOG_DEBUG << "getId() called"; | ||
| if (!mDataChannelPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "getId() called on destroyed channel").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| return Napi::Number::New(info.Env(), mDataChannelPtr->id().value_or(-1)); | ||
| return Napi::Number::New(info.Env(), mDataChannelPtr->id().value_or(-1)); | ||
| } | ||
@@ -162,10 +161,10 @@ | ||
| { | ||
| PLOG_DEBUG << "getProtocol() called"; | ||
| if (!mDataChannelPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "getProtocol() called on destroyed channel").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| PLOG_DEBUG << "getProtocol() called"; | ||
| if (!mDataChannelPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "getProtocol() called on destroyed channel").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| return Napi::String::New(info.Env(), mDataChannelPtr->protocol()); | ||
| return Napi::String::New(info.Env(), mDataChannelPtr->protocol()); | ||
| } | ||
@@ -175,28 +174,29 @@ | ||
| { | ||
| PLOG_DEBUG << "sendMessage() called"; | ||
| if (!mDataChannelPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "sendMessage() called on destroyed channel").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| PLOG_DEBUG << "sendMessage() called"; | ||
| if (!mDataChannelPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "sendMessage() called on destroyed channel").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| // Allow call with NULL | ||
| if (length < 1 || (!info[0].IsString() && !info[0].IsNull())) | ||
| { | ||
| Napi::TypeError::New(env, "String or Null expected").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| // Allow call with NULL | ||
| if (length < 1 || (!info[0].IsString() && !info[0].IsNull())) | ||
| { | ||
| Napi::TypeError::New(env, "String or Null expected").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| try | ||
| { | ||
| return Napi::Boolean::New(info.Env(), mDataChannelPtr->send(info[0].As<Napi::String>().ToString())); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error while sending data channel message: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Boolean::New(info.Env(), false); | ||
| } | ||
| try | ||
| { | ||
| return Napi::Boolean::New(info.Env(), mDataChannelPtr->send(info[0].As<Napi::String>().ToString())); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error while sending data channel message: ") + ex.what()) | ||
| .ThrowAsJavaScriptException(); | ||
| return Napi::Boolean::New(info.Env(), false); | ||
| } | ||
| } | ||
@@ -206,28 +206,29 @@ | ||
| { | ||
| PLOG_DEBUG << "sendMessageBinary() called"; | ||
| if (!mDataChannelPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "sendMessagBinary() called on destroyed channel").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| PLOG_DEBUG << "sendMessageBinary() called"; | ||
| if (!mDataChannelPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "sendMessagBinary() called on destroyed channel").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsBuffer()) | ||
| { | ||
| Napi::TypeError::New(env, "Buffer expected").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| if (length < 1 || !info[0].IsBuffer()) | ||
| { | ||
| Napi::TypeError::New(env, "Buffer expected").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| try | ||
| { | ||
| Napi::Uint8Array buffer = info[0].As<Napi::Uint8Array>(); | ||
| return Napi::Boolean::New(info.Env(), mDataChannelPtr->send((std::byte *)buffer.Data(), buffer.ByteLength())); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error while sending data channel message: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Boolean::New(info.Env(), false); | ||
| } | ||
| try | ||
| { | ||
| Napi::Uint8Array buffer = info[0].As<Napi::Uint8Array>(); | ||
| return Napi::Boolean::New(info.Env(), mDataChannelPtr->send((std::byte *)buffer.Data(), buffer.ByteLength())); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error while sending data channel message: ") + ex.what()) | ||
| .ThrowAsJavaScriptException(); | ||
| return Napi::Boolean::New(info.Env(), false); | ||
| } | ||
| } | ||
@@ -237,19 +238,19 @@ | ||
| { | ||
| PLOG_DEBUG << "isOpen() called"; | ||
| Napi::Env env = info.Env(); | ||
| PLOG_DEBUG << "isOpen() called"; | ||
| Napi::Env env = info.Env(); | ||
| if (!mDataChannelPtr) | ||
| { | ||
| return Napi::Boolean::New(info.Env(), false); | ||
| } | ||
| if (!mDataChannelPtr) | ||
| { | ||
| return Napi::Boolean::New(info.Env(), false); | ||
| } | ||
| try | ||
| { | ||
| return Napi::Boolean::New(info.Env(), mDataChannelPtr->isOpen()); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Boolean::New(info.Env(), false); | ||
| } | ||
| try | ||
| { | ||
| return Napi::Boolean::New(info.Env(), mDataChannelPtr->isOpen()); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Boolean::New(info.Env(), false); | ||
| } | ||
| } | ||
@@ -259,19 +260,19 @@ | ||
| { | ||
| PLOG_DEBUG << "bufferedAmount() called"; | ||
| Napi::Env env = info.Env(); | ||
| PLOG_DEBUG << "bufferedAmount() called"; | ||
| Napi::Env env = info.Env(); | ||
| if (!mDataChannelPtr) | ||
| { | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| if (!mDataChannelPtr) | ||
| { | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| try | ||
| { | ||
| return Napi::Number::New(info.Env(), mDataChannelPtr->bufferedAmount()); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| try | ||
| { | ||
| return Napi::Number::New(info.Env(), mDataChannelPtr->bufferedAmount()); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| } | ||
@@ -281,19 +282,19 @@ | ||
| { | ||
| PLOG_DEBUG << "maxMessageSize() called"; | ||
| Napi::Env env = info.Env(); | ||
| PLOG_DEBUG << "maxMessageSize() called"; | ||
| Napi::Env env = info.Env(); | ||
| if (!mDataChannelPtr) | ||
| { | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| if (!mDataChannelPtr) | ||
| { | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| try | ||
| { | ||
| return Napi::Number::New(info.Env(), mDataChannelPtr->maxMessageSize()); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| try | ||
| { | ||
| return Napi::Number::New(info.Env(), mDataChannelPtr->maxMessageSize()); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| } | ||
@@ -303,27 +304,28 @@ | ||
| { | ||
| PLOG_DEBUG << "setBufferedAmountLowThreshold() called"; | ||
| if (!mDataChannelPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "setBufferedAmountLowThreshold() called on destroyed channel").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| PLOG_DEBUG << "setBufferedAmountLowThreshold() called"; | ||
| if (!mDataChannelPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "setBufferedAmountLowThreshold() called on destroyed channel") | ||
| .ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "Number expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "Number expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| try | ||
| { | ||
| mDataChannelPtr->setBufferedAmountLowThreshold(info[0].ToNumber().Uint32Value()); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| try | ||
| { | ||
| mDataChannelPtr->setBufferedAmountLowThreshold(info[0].ToNumber().Uint32Value()); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| } | ||
@@ -333,36 +335,40 @@ | ||
| { | ||
| PLOG_DEBUG << "onOpen() called"; | ||
| if (!mDataChannelPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "onOpen() called on destroyed channel").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| PLOG_DEBUG << "onOpen() called"; | ||
| if (!mDataChannelPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "onOpen() called on destroyed channel").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| // Callback | ||
| mOnOpenCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| // Callback | ||
| mOnOpenCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| mDataChannelPtr->onOpen([&]() | ||
| { | ||
| PLOG_DEBUG << "onOpen cb received from rtc"; | ||
| if (mOnOpenCallback) | ||
| mOnOpenCallback->call([this](Napi::Env env, std::vector<napi_value> &args) { | ||
| PLOG_DEBUG << "mOnOpenCallback call(1)"; | ||
| // Check the data channel is not closed | ||
| if(instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| mDataChannelPtr->onOpen( | ||
| [&]() | ||
| { | ||
| PLOG_DEBUG << "onOpen cb received from rtc"; | ||
| if (mOnOpenCallback) | ||
| mOnOpenCallback->call( | ||
| [this](Napi::Env env, std::vector<napi_value> &args) | ||
| { | ||
| PLOG_DEBUG << "mOnOpenCallback call(1)"; | ||
| // Check the data channel is not closed | ||
| if (instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| args = {}; | ||
| PLOG_DEBUG << "mOnOpenCallback call(2)"; | ||
| }); }); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| args = {}; | ||
| PLOG_DEBUG << "mOnOpenCallback call(2)"; | ||
| }); | ||
| }); | ||
| } | ||
@@ -372,35 +378,38 @@ | ||
| { | ||
| PLOG_DEBUG << "onClosed() called"; | ||
| if (!mDataChannelPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "onClosed() called on destroyed channel").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| PLOG_DEBUG << "onClosed() called"; | ||
| if (!mDataChannelPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "onClosed() called on destroyed channel").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| // Callback | ||
| mOnClosedCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| // Callback | ||
| mOnClosedCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| mDataChannelPtr->onClosed([&]() | ||
| { | ||
| PLOG_DEBUG << "onClosed cb received from rtc"; | ||
| if (mOnClosedCallback) | ||
| mOnClosedCallback->call([this](Napi::Env env, std::vector<napi_value> &args) { | ||
| PLOG_DEBUG << "mOnClosedCallback call"; | ||
| // Do not check if the data channel has been closed here | ||
| mDataChannelPtr->onClosed( | ||
| [&]() | ||
| { | ||
| PLOG_DEBUG << "onClosed cb received from rtc"; | ||
| if (mOnClosedCallback) | ||
| mOnClosedCallback->call( | ||
| [this](Napi::Env env, std::vector<napi_value> &args) | ||
| { | ||
| PLOG_DEBUG << "mOnClosedCallback call"; | ||
| // Do not check if the data channel has been closed here | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| args = {}; | ||
| },[this]{ | ||
| doCleanup(); | ||
| }); }); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| args = {}; | ||
| }, | ||
| [this] { doCleanup(); }); | ||
| }); | ||
| } | ||
@@ -410,36 +419,40 @@ | ||
| { | ||
| PLOG_DEBUG << "onError() called"; | ||
| if (!mDataChannelPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "onError() called on destroyed channel").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| PLOG_DEBUG << "onError() called"; | ||
| if (!mDataChannelPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "onError() called on destroyed channel").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| // Callback | ||
| mOnErrorCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| // Callback | ||
| mOnErrorCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| mDataChannelPtr->onError([&](std::string error) | ||
| { | ||
| PLOG_DEBUG << "onError cb received from rtc"; | ||
| if (mOnErrorCallback) | ||
| mOnErrorCallback->call([this, error = std::move(error)](Napi::Env env, std::vector<napi_value> &args) { | ||
| PLOG_DEBUG << "mOnErrorCallback call(1)"; | ||
| // Check the data channel is not closed | ||
| if(instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| mDataChannelPtr->onError( | ||
| [&](std::string error) | ||
| { | ||
| PLOG_DEBUG << "onError cb received from rtc"; | ||
| if (mOnErrorCallback) | ||
| mOnErrorCallback->call( | ||
| [this, error = std::move(error)](Napi::Env env, std::vector<napi_value> &args) | ||
| { | ||
| PLOG_DEBUG << "mOnErrorCallback call(1)"; | ||
| // Check the data channel is not closed | ||
| if (instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| args = {Napi::String::New(env, error)}; | ||
| PLOG_DEBUG << "mOnErrorCallback call(2)"; | ||
| }); }); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| args = {Napi::String::New(env, error)}; | ||
| PLOG_DEBUG << "mOnErrorCallback call(2)"; | ||
| }); | ||
| }); | ||
| } | ||
@@ -449,36 +462,40 @@ | ||
| { | ||
| PLOG_DEBUG << "onBufferedAmountLow() called"; | ||
| if (!mDataChannelPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "onBufferedAmountLow() called on destroyed channel").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| PLOG_DEBUG << "onBufferedAmountLow() called"; | ||
| if (!mDataChannelPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "onBufferedAmountLow() called on destroyed channel").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| // Callback | ||
| mOnBufferedAmountLowCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| // Callback | ||
| mOnBufferedAmountLowCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| mDataChannelPtr->onBufferedAmountLow([&]() | ||
| { | ||
| PLOG_DEBUG << "onBufferedAmountLow cb received from rtc"; | ||
| if (mOnBufferedAmountLowCallback) | ||
| mOnBufferedAmountLowCallback->call([this](Napi::Env env, std::vector<napi_value> &args) { | ||
| PLOG_DEBUG << "mOnBufferedAmountLowCallback call(1)"; | ||
| // Check the data channel is not closed | ||
| if(instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| mDataChannelPtr->onBufferedAmountLow( | ||
| [&]() | ||
| { | ||
| PLOG_DEBUG << "onBufferedAmountLow cb received from rtc"; | ||
| if (mOnBufferedAmountLowCallback) | ||
| mOnBufferedAmountLowCallback->call( | ||
| [this](Napi::Env env, std::vector<napi_value> &args) | ||
| { | ||
| PLOG_DEBUG << "mOnBufferedAmountLowCallback call(1)"; | ||
| // Check the data channel is not closed | ||
| if (instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| args = {}; | ||
| PLOG_DEBUG << "mOnBufferedAmountLowCallback call(2)"; | ||
| }); }); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| args = {}; | ||
| PLOG_DEBUG << "mOnBufferedAmountLowCallback call(2)"; | ||
| }); | ||
| }); | ||
| } | ||
@@ -488,44 +505,48 @@ | ||
| { | ||
| PLOG_DEBUG << "onMessage() called"; | ||
| if (!mDataChannelPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "onMessage() called on destroyed channel").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| PLOG_DEBUG << "onMessage() called"; | ||
| if (!mDataChannelPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "onMessage() called on destroyed channel").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| // Callback | ||
| mOnMessageCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| // Callback | ||
| mOnMessageCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| mDataChannelPtr->onMessage([&](std::variant<rtc::binary, std::string> message) | ||
| { | ||
| PLOG_DEBUG << "onMessage cb received from rtc"; | ||
| if (mOnMessageCallback) | ||
| mOnMessageCallback->call([this, message = std::move(message)](Napi::Env env, std::vector<napi_value> &args) { | ||
| PLOG_DEBUG << "mOnMessageCallback call(1)"; | ||
| // Check the data channel is not closed | ||
| if(instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| mDataChannelPtr->onMessage( | ||
| [&](std::variant<rtc::binary, std::string> message) | ||
| { | ||
| PLOG_DEBUG << "onMessage cb received from rtc"; | ||
| if (mOnMessageCallback) | ||
| mOnMessageCallback->call( | ||
| [this, message = std::move(message)](Napi::Env env, std::vector<napi_value> &args) | ||
| { | ||
| PLOG_DEBUG << "mOnMessageCallback call(1)"; | ||
| // Check the data channel is not closed | ||
| if (instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| if (std::holds_alternative<std::string>(message)) | ||
| { | ||
| args = {Napi::String::New(env, std::get<std::string>(message))}; | ||
| } | ||
| else | ||
| { | ||
| auto bin = std::get<rtc::binary>(std::move(message)); | ||
| args = {Napi::Buffer<std::byte>::Copy(env, bin.data(), bin.size())}; | ||
| } | ||
| PLOG_DEBUG << "mOnMessageCallback call(2)"; | ||
| }); }); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| if (std::holds_alternative<std::string>(message)) | ||
| { | ||
| args = {Napi::String::New(env, std::get<std::string>(message))}; | ||
| } | ||
| else | ||
| { | ||
| auto bin = std::get<rtc::binary>(std::move(message)); | ||
| args = {Napi::Buffer<std::byte>::Copy(env, bin.data(), bin.size())}; | ||
| } | ||
| PLOG_DEBUG << "mOnMessageCallback call(2)"; | ||
| }); | ||
| }); | ||
| } |
| #ifndef DATA_CHANNEL_WRAPPER_H | ||
| #define DATA_CHANNEL_WRAPPER_H | ||
| #include <iostream> | ||
| #include <string> | ||
| #include <variant> | ||
| #include <memory> | ||
@@ -8,0 +6,0 @@ #include <unordered_set> |
@@ -6,3 +6,2 @@ #include "ice-udp-mux-listener-wrapper.h" | ||
| #include <cctype> | ||
| #include <sstream> | ||
@@ -14,6 +13,6 @@ Napi::FunctionReference IceUdpMuxListenerWrapper::constructor = Napi::FunctionReference(); | ||
| { | ||
| PLOG_DEBUG << "IceUdpMuxListenerWrapper StopAll() called"; | ||
| auto copy(instances); | ||
| for (auto inst : copy) | ||
| inst->doCleanup(); | ||
| PLOG_DEBUG << "IceUdpMuxListenerWrapper StopAll() called"; | ||
| auto copy(instances); | ||
| for (auto inst : copy) | ||
| inst->doCleanup(); | ||
| } | ||
@@ -23,47 +22,49 @@ | ||
| { | ||
| Napi::HandleScope scope(env); | ||
| Napi::HandleScope scope(env); | ||
| Napi::Function func = DefineClass( | ||
| env, | ||
| "IceUdpMuxListener", | ||
| { | ||
| InstanceMethod("stop", &IceUdpMuxListenerWrapper::stop), | ||
| InstanceMethod("onUnhandledStunRequest", &IceUdpMuxListenerWrapper::onUnhandledStunRequest), | ||
| InstanceMethod("port", &IceUdpMuxListenerWrapper::port), | ||
| InstanceMethod("address", &IceUdpMuxListenerWrapper::address) | ||
| }); | ||
| Napi::Function func = | ||
| DefineClass(env, "IceUdpMuxListener", | ||
| {InstanceMethod("stop", &IceUdpMuxListenerWrapper::stop), | ||
| InstanceMethod("onUnhandledStunRequest", &IceUdpMuxListenerWrapper::onUnhandledStunRequest), | ||
| InstanceMethod("port", &IceUdpMuxListenerWrapper::port), | ||
| InstanceMethod("address", &IceUdpMuxListenerWrapper::address)}); | ||
| // If this is not the first call, we don't want to reassign the constructor (hot-reload problem) | ||
| if(constructor.IsEmpty()) | ||
| { | ||
| constructor = Napi::Persistent(func); | ||
| constructor.SuppressDestruct(); | ||
| } | ||
| // If this is not the first call, we don't want to reassign the constructor (hot-reload problem) | ||
| if (constructor.IsEmpty()) | ||
| { | ||
| constructor = Napi::Persistent(func); | ||
| constructor.SuppressDestruct(); | ||
| } | ||
| exports.Set("IceUdpMuxListener", func); | ||
| return exports; | ||
| exports.Set("IceUdpMuxListener", func); | ||
| return exports; | ||
| } | ||
| IceUdpMuxListenerWrapper::IceUdpMuxListenerWrapper(const Napi::CallbackInfo &info) : Napi::ObjectWrap<IceUdpMuxListenerWrapper>(info) | ||
| IceUdpMuxListenerWrapper::IceUdpMuxListenerWrapper(const Napi::CallbackInfo &info) | ||
| : Napi::ObjectWrap<IceUdpMuxListenerWrapper>(info) | ||
| { | ||
| PLOG_DEBUG << "IceUdpMuxListenerWrapper Constructor called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| PLOG_DEBUG << "IceUdpMuxListenerWrapper Constructor called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| // We expect (Number, String?) as param | ||
| if (length > 0 && info[0].IsNumber()) { | ||
| // Port | ||
| mPort = info[0].As<Napi::Number>().ToNumber().Uint32Value(); | ||
| } else { | ||
| Napi::TypeError::New(env, "Port (Number) and optional Address (String) expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| // We expect (Number, String?) as param | ||
| if (length > 0 && info[0].IsNumber()) | ||
| { | ||
| // Port | ||
| mPort = info[0].As<Napi::Number>().ToNumber().Uint32Value(); | ||
| } | ||
| else | ||
| { | ||
| Napi::TypeError::New(env, "Port (Number) and optional Address (String) expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length > 1 && info[1].IsString()) { | ||
| // Address | ||
| mAddress = info[1].As<Napi::String>().ToString(); | ||
| } | ||
| if (length > 1 && info[1].IsString()) | ||
| { | ||
| // Address | ||
| mAddress = info[1].As<Napi::String>().ToString(); | ||
| } | ||
| iceUdpMuxListenerPtr = std::make_unique<rtc::IceUdpMuxListener>(mPort, mAddress); | ||
| instances.insert(this); | ||
| iceUdpMuxListenerPtr = std::make_unique<rtc::IceUdpMuxListener>(mPort, mAddress); | ||
| instances.insert(this); | ||
| } | ||
@@ -73,4 +74,4 @@ | ||
| { | ||
| PLOG_DEBUG << "IceUdpMuxListenerWrapper Destructor called"; | ||
| doCleanup(); | ||
| PLOG_DEBUG << "IceUdpMuxListenerWrapper Destructor called"; | ||
| doCleanup(); | ||
| } | ||
@@ -80,12 +81,12 @@ | ||
| { | ||
| PLOG_DEBUG << "IceUdpMuxListenerWrapper::doCleanup() called"; | ||
| PLOG_DEBUG << "IceUdpMuxListenerWrapper::doCleanup() called"; | ||
| if (iceUdpMuxListenerPtr) | ||
| { | ||
| iceUdpMuxListenerPtr->stop(); | ||
| iceUdpMuxListenerPtr.reset(); | ||
| } | ||
| if (iceUdpMuxListenerPtr) | ||
| { | ||
| iceUdpMuxListenerPtr->stop(); | ||
| iceUdpMuxListenerPtr.reset(); | ||
| } | ||
| mOnUnhandledStunRequestCallback.reset(); | ||
| instances.erase(this); | ||
| mOnUnhandledStunRequestCallback.reset(); | ||
| instances.erase(this); | ||
| } | ||
@@ -95,5 +96,5 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| Napi::Env env = info.Env(); | ||
| return Napi::Number::New(env, mPort); | ||
| return Napi::Number::New(env, mPort); | ||
| } | ||
@@ -103,9 +104,10 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| Napi::Env env = info.Env(); | ||
| if (!mAddress.has_value()) { | ||
| return env.Undefined(); | ||
| } | ||
| if (!mAddress.has_value()) | ||
| { | ||
| return env.Undefined(); | ||
| } | ||
| return Napi::String::New(env, mAddress.value()); | ||
| return Napi::String::New(env, mAddress.value()); | ||
| } | ||
@@ -121,38 +123,43 @@ | ||
| { | ||
| PLOG_DEBUG << "IceUdpMuxListenerWrapper::onUnhandledStunRequest() called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| PLOG_DEBUG << "IceUdpMuxListenerWrapper::onUnhandledStunRequest() called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (!iceUdpMuxListenerPtr) | ||
| { | ||
| Napi::Error::New(env, "IceUdpMuxListenerWrapper::onUnhandledStunRequest() called on destroyed IceUdpMuxListener").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (!iceUdpMuxListenerPtr) | ||
| { | ||
| Napi::Error::New(env, "IceUdpMuxListenerWrapper::onUnhandledStunRequest() called on destroyed IceUdpMuxListener") | ||
| .ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| // Callback | ||
| mOnUnhandledStunRequestCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| // Callback | ||
| mOnUnhandledStunRequestCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| iceUdpMuxListenerPtr->OnUnhandledStunRequest([&](rtc::IceUdpMuxRequest request) | ||
| iceUdpMuxListenerPtr->OnUnhandledStunRequest( | ||
| [&](rtc::IceUdpMuxRequest request) | ||
| { | ||
| PLOG_DEBUG << "IceUdpMuxListenerWrapper::onUnhandledStunRequest() IceUdpMuxCallback call(1)"; | ||
| if (mOnUnhandledStunRequestCallback) | ||
| { | ||
| PLOG_DEBUG << "IceUdpMuxListenerWrapper::onUnhandledStunRequest() IceUdpMuxCallback call(1)"; | ||
| mOnUnhandledStunRequestCallback->call( | ||
| [request = std::move(request)](Napi::Env env, std::vector<napi_value> &args) | ||
| { | ||
| Napi::Object reqObj = Napi::Object::New(env); | ||
| reqObj.Set("ufrag", request.remoteUfrag.c_str()); | ||
| reqObj.Set("host", request.remoteAddress.c_str()); | ||
| reqObj.Set("port", request.remotePort); | ||
| if (mOnUnhandledStunRequestCallback) { | ||
| mOnUnhandledStunRequestCallback->call([request = std::move(request)](Napi::Env env, std::vector<napi_value> &args) { | ||
| Napi::Object reqObj = Napi::Object::New(env); | ||
| reqObj.Set("ufrag", request.remoteUfrag.c_str()); | ||
| reqObj.Set("host", request.remoteAddress.c_str()); | ||
| reqObj.Set("port", request.remotePort); | ||
| args = {reqObj}; | ||
| }); | ||
| } | ||
| args = {reqObj}; | ||
| }); | ||
| } | ||
| PLOG_DEBUG << "IceUdpMuxListenerWrapper::onUnhandledStunRequest() IceUdpMuxCallback call(2)"; | ||
| }); | ||
| PLOG_DEBUG << "IceUdpMuxListenerWrapper::onUnhandledStunRequest() IceUdpMuxCallback call(2)"; | ||
| }); | ||
| } |
+11
-11
@@ -21,23 +21,23 @@ #include <napi.h> | ||
| { | ||
| RtcWrapper::Init(env, exports); | ||
| RtcWrapper::Init(env, exports); | ||
| #if RTC_ENABLE_MEDIA == 1 | ||
| RtcpReceivingSessionWrapper::Init(env, exports); | ||
| TrackWrapper::Init(env, exports); | ||
| VideoWrapper::Init(env, exports); | ||
| AudioWrapper::Init(env, exports); | ||
| RtcpReceivingSessionWrapper::Init(env, exports); | ||
| TrackWrapper::Init(env, exports); | ||
| VideoWrapper::Init(env, exports); | ||
| AudioWrapper::Init(env, exports); | ||
| #endif | ||
| DataChannelWrapper::Init(env, exports); | ||
| IceUdpMuxListenerWrapper::Init(env, exports); | ||
| PeerConnectionWrapper::Init(env, exports); | ||
| DataChannelWrapper::Init(env, exports); | ||
| IceUdpMuxListenerWrapper::Init(env, exports); | ||
| PeerConnectionWrapper::Init(env, exports); | ||
| #if RTC_ENABLE_WEBSOCKET == 1 | ||
| WebSocketWrapper::Init(env, exports); | ||
| WebSocketServerWrapper::Init(env, exports); | ||
| WebSocketWrapper::Init(env, exports); | ||
| WebSocketServerWrapper::Init(env, exports); | ||
| #endif | ||
| return exports; | ||
| return exports; | ||
| } | ||
| NODE_API_MODULE(nodeDataChannel, InitAll) |
+277
-280
@@ -9,40 +9,38 @@ #include "media-audio-wrapper.h" | ||
| { | ||
| Napi::HandleScope scope(env); | ||
| Napi::HandleScope scope(env); | ||
| Napi::Function func = DefineClass( | ||
| env, | ||
| "Audio", | ||
| { | ||
| InstanceValue("media-type-audio", Napi::Boolean::New(env, true)), | ||
| InstanceMethod("addAudioCodec", &AudioWrapper::addAudioCodec), | ||
| InstanceMethod("addOpusCodec", &AudioWrapper::addOpusCodec), | ||
| InstanceMethod("direction", &AudioWrapper::direction), | ||
| InstanceMethod("generateSdp", &AudioWrapper::generateSdp), | ||
| InstanceMethod("mid", &AudioWrapper::mid), | ||
| InstanceMethod("setDirection", &AudioWrapper::setDirection), | ||
| InstanceMethod("description", &AudioWrapper::description), | ||
| InstanceMethod("removeFormat", &AudioWrapper::removeFormat), | ||
| InstanceMethod("addSSRC", &AudioWrapper::addSSRC), | ||
| InstanceMethod("removeSSRC", &AudioWrapper::removeSSRC), | ||
| InstanceMethod("replaceSSRC", &AudioWrapper::replaceSSRC), | ||
| InstanceMethod("hasSSRC", &AudioWrapper::hasSSRC), | ||
| InstanceMethod("getSSRCs", &AudioWrapper::getSSRCs), | ||
| InstanceMethod("getCNameForSsrc", &AudioWrapper::getCNameForSsrc), | ||
| InstanceMethod("setBitrate", &AudioWrapper::setBitrate), | ||
| InstanceMethod("getBitrate", &AudioWrapper::getBitrate), | ||
| InstanceMethod("hasPayloadType", &AudioWrapper::hasPayloadType), | ||
| InstanceMethod("addRTXCodec", &AudioWrapper::addRTXCodec), | ||
| InstanceMethod("addRTPMap", &AudioWrapper::addRTPMap), | ||
| InstanceMethod("parseSdpLine", &AudioWrapper::parseSdpLine), | ||
| }); | ||
| Napi::Function func = DefineClass(env, "Audio", | ||
| { | ||
| InstanceValue("media-type-audio", Napi::Boolean::New(env, true)), | ||
| InstanceMethod("addAudioCodec", &AudioWrapper::addAudioCodec), | ||
| InstanceMethod("addOpusCodec", &AudioWrapper::addOpusCodec), | ||
| InstanceMethod("direction", &AudioWrapper::direction), | ||
| InstanceMethod("generateSdp", &AudioWrapper::generateSdp), | ||
| InstanceMethod("mid", &AudioWrapper::mid), | ||
| InstanceMethod("setDirection", &AudioWrapper::setDirection), | ||
| InstanceMethod("description", &AudioWrapper::description), | ||
| InstanceMethod("removeFormat", &AudioWrapper::removeFormat), | ||
| InstanceMethod("addSSRC", &AudioWrapper::addSSRC), | ||
| InstanceMethod("removeSSRC", &AudioWrapper::removeSSRC), | ||
| InstanceMethod("replaceSSRC", &AudioWrapper::replaceSSRC), | ||
| InstanceMethod("hasSSRC", &AudioWrapper::hasSSRC), | ||
| InstanceMethod("getSSRCs", &AudioWrapper::getSSRCs), | ||
| InstanceMethod("getCNameForSsrc", &AudioWrapper::getCNameForSsrc), | ||
| InstanceMethod("setBitrate", &AudioWrapper::setBitrate), | ||
| InstanceMethod("getBitrate", &AudioWrapper::getBitrate), | ||
| InstanceMethod("hasPayloadType", &AudioWrapper::hasPayloadType), | ||
| InstanceMethod("addRTXCodec", &AudioWrapper::addRTXCodec), | ||
| InstanceMethod("addRTPMap", &AudioWrapper::addRTPMap), | ||
| InstanceMethod("parseSdpLine", &AudioWrapper::parseSdpLine), | ||
| }); | ||
| // If this is not the first call, we don't want to reassign the constructor (hot-reload problem) | ||
| if(constructor.IsEmpty()) | ||
| { | ||
| constructor = Napi::Persistent(func); | ||
| constructor.SuppressDestruct(); | ||
| } | ||
| // If this is not the first call, we don't want to reassign the constructor (hot-reload problem) | ||
| if (constructor.IsEmpty()) | ||
| { | ||
| constructor = Napi::Persistent(func); | ||
| constructor.SuppressDestruct(); | ||
| } | ||
| exports.Set("Audio", func); | ||
| return exports; | ||
| exports.Set("Audio", func); | ||
| return exports; | ||
| } | ||
@@ -52,35 +50,35 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| std::string mid = "audio"; | ||
| rtc::Description::Direction dir = rtc::Description::Direction::SendOnly; | ||
| std::string mid = "audio"; | ||
| rtc::Description::Direction dir = rtc::Description::Direction::SendOnly; | ||
| // optional | ||
| if (length > 0) | ||
| // optional | ||
| if (length > 0) | ||
| { | ||
| if (!info[0].IsString()) | ||
| { | ||
| if (!info[0].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "mid (String) expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| mid = info[0].As<Napi::String>().ToString(); | ||
| Napi::TypeError::New(env, "mid (String) expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| mid = info[0].As<Napi::String>().ToString(); | ||
| } | ||
| // ootional | ||
| if (length > 1) | ||
| // ootional | ||
| if (length > 1) | ||
| { | ||
| if (!info[1].IsString()) | ||
| { | ||
| if (!info[1].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "direction (String) expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| std::string dirAsStr = info[1].As<Napi::String>().ToString(); | ||
| dir = strToDirection(dirAsStr); | ||
| Napi::TypeError::New(env, "direction (String) expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| mAudioPtr = std::make_unique<rtc::Description::Audio>(mid, dir); | ||
| std::string dirAsStr = info[1].As<Napi::String>().ToString(); | ||
| dir = strToDirection(dirAsStr); | ||
| } | ||
| instances.insert(this); | ||
| mAudioPtr = std::make_unique<rtc::Description::Audio>(mid, dir); | ||
| instances.insert(this); | ||
| } | ||
@@ -90,37 +88,34 @@ | ||
| { | ||
| mAudioPtr.reset(); | ||
| instances.erase(this); | ||
| mAudioPtr.reset(); | ||
| instances.erase(this); | ||
| } | ||
| rtc::Description::Audio AudioWrapper::getAudioInstance() | ||
| { | ||
| return *(mAudioPtr.get()); | ||
| } | ||
| rtc::Description::Audio AudioWrapper::getAudioInstance() { return *(mAudioPtr.get()); } | ||
| void AudioWrapper::addAudioCodec(const Napi::CallbackInfo &info) | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 2 || !info[0].IsNumber() || !info[1].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number, String, String[optional]) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 2 || !info[0].IsNumber() || !info[1].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number, String, String[optional]) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| int payloadType = info[0].As<Napi::Number>().ToNumber(); | ||
| std::string codec = info[1].As<Napi::String>().ToString(); | ||
| std::optional<std::string> profile = std::nullopt; | ||
| int payloadType = info[0].As<Napi::Number>().ToNumber(); | ||
| std::string codec = info[1].As<Napi::String>().ToString(); | ||
| std::optional<std::string> profile = std::nullopt; | ||
| if (length > 2) | ||
| if (length > 2) | ||
| { | ||
| if (!info[2].IsString()) | ||
| { | ||
| if (!info[2].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "profile (String) expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| profile = info[2].As<Napi::String>().ToString(); | ||
| Napi::TypeError::New(env, "profile (String) expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| profile = info[2].As<Napi::String>().ToString(); | ||
| } | ||
| mAudioPtr->addAudioCodec(payloadType, codec, profile); | ||
| mAudioPtr->addAudioCodec(payloadType, codec, profile); | ||
| } | ||
@@ -130,32 +125,31 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| int payloadType = info[0].As<Napi::Number>().ToNumber(); | ||
| std::optional<std::string> profile = rtc::DEFAULT_OPUS_AUDIO_PROFILE; | ||
| int payloadType = info[0].As<Napi::Number>().ToNumber(); | ||
| std::optional<std::string> profile = rtc::DEFAULT_OPUS_AUDIO_PROFILE; | ||
| if (length > 1) | ||
| if (length > 1) | ||
| { | ||
| if (!info[1].IsString()) | ||
| { | ||
| if (!info[1].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "profile (String) expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| profile = info[1].As<Napi::String>().ToString(); | ||
| Napi::TypeError::New(env, "profile (String) expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| profile = info[1].As<Napi::String>().ToString(); | ||
| } | ||
| mAudioPtr->addOpusCodec(payloadType, profile); | ||
| mAudioPtr->addOpusCodec(payloadType, profile); | ||
| } | ||
| Napi::Value AudioWrapper::direction(const Napi::CallbackInfo &info) | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| return Napi::String::New(env, directionToStr(mAudioPtr->direction())); | ||
| Napi::Env env = info.Env(); | ||
| return Napi::String::New(env, directionToStr(mAudioPtr->direction())); | ||
| } | ||
@@ -165,16 +159,16 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 3 || !info[0].IsString() || !info[1].IsString() || !info[2].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (String, String, Number) as param").ThrowAsJavaScriptException(); | ||
| return Napi::String::New(env, ""); | ||
| } | ||
| if (length < 3 || !info[0].IsString() || !info[1].IsString() || !info[2].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (String, String, Number) as param").ThrowAsJavaScriptException(); | ||
| return Napi::String::New(env, ""); | ||
| } | ||
| std::string eol = info[0].As<Napi::String>().ToString(); | ||
| std::string addr = info[1].As<Napi::String>().ToString(); | ||
| uint16_t port = info[2].As<Napi::Number>().Uint32Value(); | ||
| std::string eol = info[0].As<Napi::String>().ToString(); | ||
| std::string addr = info[1].As<Napi::String>().ToString(); | ||
| uint16_t port = info[2].As<Napi::Number>().Uint32Value(); | ||
| return Napi::String::New(env, mAudioPtr->generateSdp(eol, addr, port)); | ||
| return Napi::String::New(env, mAudioPtr->generateSdp(eol, addr, port)); | ||
| } | ||
@@ -184,4 +178,4 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| return Napi::String::New(env, mAudioPtr->mid()); | ||
| Napi::Env env = info.Env(); | ||
| return Napi::String::New(env, mAudioPtr->mid()); | ||
| } | ||
@@ -191,14 +185,14 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (String) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (String) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| std::string dirAsStr = info[0].As<Napi::String>().ToString(); | ||
| rtc::Description::Direction dir = strToDirection(dirAsStr); | ||
| mAudioPtr->setDirection(dir); | ||
| std::string dirAsStr = info[0].As<Napi::String>().ToString(); | ||
| rtc::Description::Direction dir = strToDirection(dirAsStr); | ||
| mAudioPtr->setDirection(dir); | ||
| } | ||
@@ -208,4 +202,4 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| return Napi::String::New(env, mAudioPtr->description()); | ||
| Napi::Env env = info.Env(); | ||
| return Napi::String::New(env, mAudioPtr->description()); | ||
| } | ||
@@ -215,14 +209,14 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (String) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (String) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| std::string fmt = info[0].As<Napi::String>().ToString(); | ||
| std::string fmt = info[0].As<Napi::String>().ToString(); | ||
| mAudioPtr->removeFormat(fmt); | ||
| mAudioPtr->removeFormat(fmt); | ||
| } | ||
@@ -232,47 +226,48 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number, String[optional], String[optional], String[optional]) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number, String[optional], String[optional], String[optional]) as param") | ||
| .ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| uint32_t ssrc = static_cast<uint32_t>(info[0].As<Napi::Number>().ToNumber()); | ||
| std::optional<std::string> name; | ||
| std::optional<std::string> msid = std::nullopt; | ||
| std::optional<std::string> trackID = std::nullopt; | ||
| uint32_t ssrc = static_cast<uint32_t>(info[0].As<Napi::Number>().ToNumber()); | ||
| std::optional<std::string> name; | ||
| std::optional<std::string> msid = std::nullopt; | ||
| std::optional<std::string> trackID = std::nullopt; | ||
| if (length > 1) | ||
| if (length > 1) | ||
| { | ||
| if (!info[1].IsString()) | ||
| { | ||
| if (!info[1].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "name as String expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| name = info[1].As<Napi::String>().ToString(); | ||
| Napi::TypeError::New(env, "name as String expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| name = info[1].As<Napi::String>().ToString(); | ||
| } | ||
| if (length > 2) | ||
| if (length > 2) | ||
| { | ||
| if (!info[2].IsString()) | ||
| { | ||
| if (!info[2].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "msid as String expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| msid = info[2].As<Napi::String>().ToString(); | ||
| Napi::TypeError::New(env, "msid as String expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| msid = info[2].As<Napi::String>().ToString(); | ||
| } | ||
| if (length > 3) | ||
| if (length > 3) | ||
| { | ||
| if (!info[3].IsString()) | ||
| { | ||
| if (!info[3].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "trackID as String expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| trackID = info[3].As<Napi::String>().ToString(); | ||
| Napi::TypeError::New(env, "trackID as String expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| trackID = info[3].As<Napi::String>().ToString(); | ||
| } | ||
| mAudioPtr->addSSRC(ssrc, name, msid, trackID); | ||
| mAudioPtr->addSSRC(ssrc, name, msid, trackID); | ||
| } | ||
@@ -282,14 +277,14 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| uint32_t ssrc = static_cast<uint32_t>(info[0].As<Napi::Number>().ToNumber()); | ||
| uint32_t ssrc = static_cast<uint32_t>(info[0].As<Napi::Number>().ToNumber()); | ||
| mAudioPtr->removeSSRC(ssrc); | ||
| mAudioPtr->removeSSRC(ssrc); | ||
| } | ||
@@ -299,48 +294,50 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 2 || !info[0].IsNumber() || !info[1].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number, Number, String[optional], String[optional], String[optional]) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 2 || !info[0].IsNumber() || !info[1].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, | ||
| "We expect (Number, Number, String[optional], String[optional], String[optional]) as param") | ||
| .ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| uint32_t ssrc = static_cast<uint32_t>(info[0].As<Napi::Number>().ToNumber()); | ||
| uint32_t oldSsrc = static_cast<uint32_t>(info[1].As<Napi::Number>().ToNumber()); | ||
| std::optional<std::string> name; | ||
| std::optional<std::string> msid = std::nullopt; | ||
| std::optional<std::string> trackID = std::nullopt; | ||
| uint32_t ssrc = static_cast<uint32_t>(info[0].As<Napi::Number>().ToNumber()); | ||
| uint32_t oldSsrc = static_cast<uint32_t>(info[1].As<Napi::Number>().ToNumber()); | ||
| std::optional<std::string> name; | ||
| std::optional<std::string> msid = std::nullopt; | ||
| std::optional<std::string> trackID = std::nullopt; | ||
| if (length > 2) | ||
| if (length > 2) | ||
| { | ||
| if (!info[2].IsString()) | ||
| { | ||
| if (!info[2].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "name as String expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| name = info[2].As<Napi::String>().ToString(); | ||
| Napi::TypeError::New(env, "name as String expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| name = info[2].As<Napi::String>().ToString(); | ||
| } | ||
| if (length > 3) | ||
| if (length > 3) | ||
| { | ||
| if (!info[3].IsString()) | ||
| { | ||
| if (!info[3].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "msid as String expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| msid = info[3].As<Napi::String>().ToString(); | ||
| Napi::TypeError::New(env, "msid as String expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| msid = info[3].As<Napi::String>().ToString(); | ||
| } | ||
| if (length > 4) | ||
| if (length > 4) | ||
| { | ||
| if (!info[4].IsString()) | ||
| { | ||
| if (!info[4].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "trackID as String expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| trackID = info[4].As<Napi::String>().ToString(); | ||
| Napi::TypeError::New(env, "trackID as String expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| trackID = info[4].As<Napi::String>().ToString(); | ||
| } | ||
| mAudioPtr->replaceSSRC(oldSsrc, ssrc, name, msid, trackID); | ||
| mAudioPtr->replaceSSRC(oldSsrc, ssrc, name, msid, trackID); | ||
| } | ||
@@ -350,14 +347,14 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number) as param").ThrowAsJavaScriptException(); | ||
| return env.Null(); | ||
| } | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number) as param").ThrowAsJavaScriptException(); | ||
| return env.Null(); | ||
| } | ||
| uint32_t ssrc = static_cast<uint32_t>(info[0].As<Napi::Number>().ToNumber()); | ||
| uint32_t ssrc = static_cast<uint32_t>(info[0].As<Napi::Number>().ToNumber()); | ||
| return Napi::Boolean::New(env, mAudioPtr->hasSSRC(ssrc)); | ||
| return Napi::Boolean::New(env, mAudioPtr->hasSSRC(ssrc)); | ||
| } | ||
@@ -367,11 +364,11 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| Napi::Env env = info.Env(); | ||
| auto list = mAudioPtr->getSSRCs(); | ||
| auto list = mAudioPtr->getSSRCs(); | ||
| Napi::Uint32Array napiArr = Napi::Uint32Array::New(env, list.size()); | ||
| for (size_t i = 0; i < list.size(); i++) | ||
| napiArr[i] = list[i]; | ||
| Napi::Uint32Array napiArr = Napi::Uint32Array::New(env, list.size()); | ||
| for (size_t i = 0; i < list.size(); i++) | ||
| napiArr[i] = list[i]; | ||
| return napiArr; | ||
| return napiArr; | ||
| } | ||
@@ -381,19 +378,19 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number) as param").ThrowAsJavaScriptException(); | ||
| return env.Null(); | ||
| } | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number) as param").ThrowAsJavaScriptException(); | ||
| return env.Null(); | ||
| } | ||
| uint32_t ssrc = static_cast<uint32_t>(info[0].As<Napi::Number>().ToNumber()); | ||
| uint32_t ssrc = static_cast<uint32_t>(info[0].As<Napi::Number>().ToNumber()); | ||
| std::optional<std::string> name = mAudioPtr->getCNameForSsrc(ssrc); | ||
| std::optional<std::string> name = mAudioPtr->getCNameForSsrc(ssrc); | ||
| if (!name.has_value()) | ||
| return env.Null(); | ||
| if (!name.has_value()) | ||
| return env.Null(); | ||
| return Napi::String::New(env, name.value()); | ||
| return Napi::String::New(env, name.value()); | ||
| } | ||
@@ -403,13 +400,13 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| unsigned int bitrate = static_cast<uint32_t>(info[0].As<Napi::Number>().ToNumber()); | ||
| mAudioPtr->setBitrate(bitrate); | ||
| unsigned int bitrate = static_cast<uint32_t>(info[0].As<Napi::Number>().ToNumber()); | ||
| mAudioPtr->setBitrate(bitrate); | ||
| } | ||
@@ -420,3 +417,3 @@ | ||
| return Napi::Number::New(info.Env(), mAudioPtr->bitrate()); | ||
| return Napi::Number::New(info.Env(), mAudioPtr->bitrate()); | ||
| } | ||
@@ -426,14 +423,14 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number) as param").ThrowAsJavaScriptException(); | ||
| return env.Null(); | ||
| } | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number) as param").ThrowAsJavaScriptException(); | ||
| return env.Null(); | ||
| } | ||
| int payloadType = static_cast<int>(info[0].As<Napi::Number>().ToNumber()); | ||
| int payloadType = static_cast<int>(info[0].As<Napi::Number>().ToNumber()); | ||
| return Napi::Boolean::New(env, mAudioPtr->hasPayloadType(payloadType)); | ||
| return Napi::Boolean::New(env, mAudioPtr->hasPayloadType(payloadType)); | ||
| } | ||
@@ -443,16 +440,16 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 3 || !info[0].IsNumber() || !info[1].IsNumber() || !info[2].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number,Number,Number) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 3 || !info[0].IsNumber() || !info[1].IsNumber() || !info[2].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number,Number,Number) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| int payloadType = static_cast<int32_t>(info[0].As<Napi::Number>().ToNumber()); | ||
| int originalPayloadType = static_cast<int32_t>(info[1].As<Napi::Number>().ToNumber()); | ||
| unsigned int clockRate = static_cast<uint32_t>(info[2].As<Napi::Number>().ToNumber()); | ||
| int payloadType = static_cast<int32_t>(info[0].As<Napi::Number>().ToNumber()); | ||
| int originalPayloadType = static_cast<int32_t>(info[1].As<Napi::Number>().ToNumber()); | ||
| unsigned int clockRate = static_cast<uint32_t>(info[2].As<Napi::Number>().ToNumber()); | ||
| mAudioPtr->addRtxCodec(payloadType, originalPayloadType, clockRate); | ||
| mAudioPtr->addRtxCodec(payloadType, originalPayloadType, clockRate); | ||
| } | ||
@@ -462,3 +459,3 @@ | ||
| { | ||
| // mAudioPtr->addRTPMap() | ||
| // mAudioPtr->addRTPMap() | ||
| } | ||
@@ -468,14 +465,14 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (String) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (String) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| std::string line = info[0].As<Napi::String>().ToString(); | ||
| std::string line = info[0].As<Napi::String>().ToString(); | ||
| mAudioPtr->parseSdpLine(line); | ||
| mAudioPtr->parseSdpLine(line); | ||
| } |
@@ -9,4 +9,2 @@ #ifndef MEDIA_AUDIO_WRAPPER_H | ||
| #include "thread-safe-callback.h" | ||
| class AudioWrapper : public Napi::ObjectWrap<AudioWrapper> | ||
@@ -55,2 +53,2 @@ { | ||
| #endif // MEDIA_AUDIO_WRAPPER_H | ||
| #endif // MEDIA_AUDIO_WRAPPER_H |
@@ -5,14 +5,14 @@ #include "media-direction.h" | ||
| { | ||
| rtc::Description::Direction dir = rtc::Description::Direction::Unknown; | ||
| rtc::Description::Direction dir = rtc::Description::Direction::Unknown; | ||
| if (dirAsStr == "SendOnly") | ||
| dir = rtc::Description::Direction::SendOnly; | ||
| if (dirAsStr == "SendRecv") | ||
| dir = rtc::Description::Direction::SendRecv; | ||
| if (dirAsStr == "RecvOnly") | ||
| dir = rtc::Description::Direction::RecvOnly; | ||
| if (dirAsStr == "Inactive") | ||
| dir = rtc::Description::Direction::Inactive; | ||
| if (dirAsStr == "SendOnly") | ||
| dir = rtc::Description::Direction::SendOnly; | ||
| if (dirAsStr == "SendRecv") | ||
| dir = rtc::Description::Direction::SendRecv; | ||
| if (dirAsStr == "RecvOnly") | ||
| dir = rtc::Description::Direction::RecvOnly; | ||
| if (dirAsStr == "Inactive") | ||
| dir = rtc::Description::Direction::Inactive; | ||
| return dir; | ||
| return dir; | ||
| } | ||
@@ -22,24 +22,24 @@ | ||
| { | ||
| std::string dirAsStr; | ||
| switch (dir) | ||
| { | ||
| case rtc::Description::Direction::Unknown: | ||
| dirAsStr = "Unknown"; | ||
| break; | ||
| case rtc::Description::Direction::SendOnly: | ||
| dirAsStr = "SendOnly"; | ||
| break; | ||
| case rtc::Description::Direction::RecvOnly: | ||
| dirAsStr = "RecvOnly"; | ||
| break; | ||
| case rtc::Description::Direction::SendRecv: | ||
| dirAsStr = "SendRecv"; | ||
| break; | ||
| case rtc::Description::Direction::Inactive: | ||
| dirAsStr = "Inactive"; | ||
| break; | ||
| default: | ||
| dirAsStr = "UNKNOWN_DIR_TYPE"; | ||
| } | ||
| return dirAsStr; | ||
| std::string dirAsStr; | ||
| switch (dir) | ||
| { | ||
| case rtc::Description::Direction::Unknown: | ||
| dirAsStr = "Unknown"; | ||
| break; | ||
| case rtc::Description::Direction::SendOnly: | ||
| dirAsStr = "SendOnly"; | ||
| break; | ||
| case rtc::Description::Direction::RecvOnly: | ||
| dirAsStr = "RecvOnly"; | ||
| break; | ||
| case rtc::Description::Direction::SendRecv: | ||
| dirAsStr = "SendRecv"; | ||
| break; | ||
| case rtc::Description::Direction::Inactive: | ||
| dirAsStr = "Inactive"; | ||
| break; | ||
| default: | ||
| dirAsStr = "UNKNOWN_DIR_TYPE"; | ||
| } | ||
| return dirAsStr; | ||
| } |
@@ -8,4 +8,4 @@ #ifndef MEDIA_DIRECTION_H | ||
| rtc::Description::Direction strToDirection(const std::string dirAsStr); | ||
| std::string directionToStr(rtc::Description::Direction dir); | ||
| std::string directionToStr(rtc::Description::Direction dir); | ||
| #endif // MEDIA_DIRECTION_H |
@@ -8,27 +8,26 @@ #include "media-rtcpreceivingsession-wrapper.h" | ||
| { | ||
| Napi::HandleScope scope(env); | ||
| Napi::HandleScope scope(env); | ||
| Napi::Function func = Napi::ObjectWrap<RtcpReceivingSessionWrapper>::DefineClass( | ||
| env, | ||
| "RtcpReceivingSession", | ||
| { | ||
| // Instance Methods | ||
| }); | ||
| Napi::Function func = Napi::ObjectWrap<RtcpReceivingSessionWrapper>::DefineClass(env, "RtcpReceivingSession", | ||
| { | ||
| // Instance Methods | ||
| }); | ||
| // If this is not the first call, we don't want to reassign the constructor (hot-reload problem) | ||
| if(constructor.IsEmpty()) | ||
| { | ||
| constructor = Napi::Persistent(func); | ||
| constructor.SuppressDestruct(); | ||
| } | ||
| // If this is not the first call, we don't want to reassign the constructor (hot-reload problem) | ||
| if (constructor.IsEmpty()) | ||
| { | ||
| constructor = Napi::Persistent(func); | ||
| constructor.SuppressDestruct(); | ||
| } | ||
| exports.Set("RtcpReceivingSession", func); | ||
| return exports; | ||
| exports.Set("RtcpReceivingSession", func); | ||
| return exports; | ||
| } | ||
| RtcpReceivingSessionWrapper::RtcpReceivingSessionWrapper(const Napi::CallbackInfo &info) : Napi::ObjectWrap<RtcpReceivingSessionWrapper>(info) | ||
| RtcpReceivingSessionWrapper::RtcpReceivingSessionWrapper(const Napi::CallbackInfo &info) | ||
| : Napi::ObjectWrap<RtcpReceivingSessionWrapper>(info) | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| mSessionPtr = std::make_unique<rtc::RtcpReceivingSession>(); | ||
| instances.insert(this); | ||
| Napi::Env env = info.Env(); | ||
| mSessionPtr = std::make_unique<rtc::RtcpReceivingSession>(); | ||
| instances.insert(this); | ||
| } | ||
@@ -38,9 +37,6 @@ | ||
| { | ||
| mSessionPtr.reset(); | ||
| instances.erase(this); | ||
| mSessionPtr.reset(); | ||
| instances.erase(this); | ||
| } | ||
| std::shared_ptr<rtc::RtcpReceivingSession> RtcpReceivingSessionWrapper::getSessionInstance() | ||
| { | ||
| return mSessionPtr; | ||
| } | ||
| std::shared_ptr<rtc::RtcpReceivingSession> RtcpReceivingSessionWrapper::getSessionInstance() { return mSessionPtr; } |
| #ifndef MEDIA_RTCPRECEIVINGSESSION_WRAPPER_H | ||
| #define MEDIA_RTCPRECEIVINGSESSION_WRAPPER_H | ||
| #include <iostream> | ||
| #include <string> | ||
| #include <variant> | ||
| #include <memory> | ||
@@ -13,4 +10,2 @@ #include <unordered_set> | ||
| #include "thread-safe-callback.h" | ||
| class RtcpReceivingSessionWrapper : public Napi::ObjectWrap<RtcpReceivingSessionWrapper> | ||
@@ -17,0 +12,0 @@ { |
+309
-298
@@ -12,5 +12,5 @@ #include "media-track-wrapper.h" | ||
| { | ||
| auto copy(instances); | ||
| for (auto inst : copy) | ||
| inst->doClose(); | ||
| auto copy(instances); | ||
| for (auto inst : copy) | ||
| inst->doClose(); | ||
| } | ||
@@ -20,6 +20,6 @@ | ||
| { | ||
| PLOG_DEBUG << "CleanupAll() called"; | ||
| auto copy(instances); | ||
| for (auto inst : copy) | ||
| inst->doCleanup(); | ||
| PLOG_DEBUG << "CleanupAll() called"; | ||
| auto copy(instances); | ||
| for (auto inst : copy) | ||
| inst->doCleanup(); | ||
| } | ||
@@ -29,35 +29,33 @@ | ||
| { | ||
| Napi::HandleScope scope(env); | ||
| Napi::HandleScope scope(env); | ||
| Napi::Function func = DefineClass( | ||
| env, | ||
| "Track", | ||
| { | ||
| InstanceMethod("direction", &TrackWrapper::direction), | ||
| InstanceMethod("mid", &TrackWrapper::mid), | ||
| InstanceMethod("type", &TrackWrapper::type), | ||
| InstanceMethod("close", &TrackWrapper::close), | ||
| InstanceMethod("sendMessage", &TrackWrapper::sendMessage), | ||
| InstanceMethod("sendMessageBinary", &TrackWrapper::sendMessageBinary), | ||
| InstanceMethod("isOpen", &TrackWrapper::isOpen), | ||
| InstanceMethod("isClosed", &TrackWrapper::isClosed), | ||
| InstanceMethod("maxMessageSize", &TrackWrapper::maxMessageSize), | ||
| InstanceMethod("requestBitrate", &TrackWrapper::requestBitrate), | ||
| InstanceMethod("requestKeyframe", &TrackWrapper::requestKeyframe), | ||
| InstanceMethod("setMediaHandler", &TrackWrapper::setMediaHandler), | ||
| InstanceMethod("onOpen", &TrackWrapper::onOpen), | ||
| InstanceMethod("onClosed", &TrackWrapper::onClosed), | ||
| InstanceMethod("onError", &TrackWrapper::onError), | ||
| InstanceMethod("onMessage", &TrackWrapper::onMessage), | ||
| }); | ||
| Napi::Function func = DefineClass(env, "Track", | ||
| { | ||
| InstanceMethod("direction", &TrackWrapper::direction), | ||
| InstanceMethod("mid", &TrackWrapper::mid), | ||
| InstanceMethod("type", &TrackWrapper::type), | ||
| InstanceMethod("close", &TrackWrapper::close), | ||
| InstanceMethod("sendMessage", &TrackWrapper::sendMessage), | ||
| InstanceMethod("sendMessageBinary", &TrackWrapper::sendMessageBinary), | ||
| InstanceMethod("isOpen", &TrackWrapper::isOpen), | ||
| InstanceMethod("isClosed", &TrackWrapper::isClosed), | ||
| InstanceMethod("maxMessageSize", &TrackWrapper::maxMessageSize), | ||
| InstanceMethod("requestBitrate", &TrackWrapper::requestBitrate), | ||
| InstanceMethod("requestKeyframe", &TrackWrapper::requestKeyframe), | ||
| InstanceMethod("setMediaHandler", &TrackWrapper::setMediaHandler), | ||
| InstanceMethod("onOpen", &TrackWrapper::onOpen), | ||
| InstanceMethod("onClosed", &TrackWrapper::onClosed), | ||
| InstanceMethod("onError", &TrackWrapper::onError), | ||
| InstanceMethod("onMessage", &TrackWrapper::onMessage), | ||
| }); | ||
| // If this is not the first call, we don't want to reassign the constructor (hot-reload problem) | ||
| if(constructor.IsEmpty()) | ||
| { | ||
| constructor = Napi::Persistent(func); | ||
| constructor.SuppressDestruct(); | ||
| } | ||
| // If this is not the first call, we don't want to reassign the constructor (hot-reload problem) | ||
| if (constructor.IsEmpty()) | ||
| { | ||
| constructor = Napi::Persistent(func); | ||
| constructor.SuppressDestruct(); | ||
| } | ||
| exports.Set("Track", func); | ||
| return exports; | ||
| exports.Set("Track", func); | ||
| return exports; | ||
| } | ||
@@ -67,36 +65,34 @@ | ||
| { | ||
| PLOG_DEBUG << "Constructor called"; | ||
| mTrackPtr = *(info[0].As<Napi::External<std::shared_ptr<rtc::Track>>>().Data()); | ||
| PLOG_DEBUG << "Track created"; | ||
| PLOG_DEBUG << "Constructor called"; | ||
| mTrackPtr = *(info[0].As<Napi::External<std::shared_ptr<rtc::Track>>>().Data()); | ||
| PLOG_DEBUG << "Track created"; | ||
| // Closed callback must be set to trigger cleanup | ||
| mOnClosedCallback = std::make_unique<ThreadSafeCallback>(Napi::Function::New(info.Env(), [](const Napi::CallbackInfo&){})); | ||
| // Closed callback must be set to trigger cleanup | ||
| mOnClosedCallback = | ||
| std::make_unique<ThreadSafeCallback>(Napi::Function::New(info.Env(), [](const Napi::CallbackInfo &) {})); | ||
| instances.insert(this); | ||
| instances.insert(this); | ||
| } | ||
| TrackWrapper::~TrackWrapper() | ||
| { | ||
| doClose(); | ||
| } | ||
| TrackWrapper::~TrackWrapper() { doClose(); } | ||
| void TrackWrapper::doClose() | ||
| { | ||
| if (mTrackPtr) | ||
| if (mTrackPtr) | ||
| { | ||
| try | ||
| { | ||
| try | ||
| { | ||
| mTrackPtr->close(); | ||
| mTrackPtr.reset(); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| std::cerr << std::string("libdatachannel error while closing track: ") + ex.what() << std::endl; | ||
| return; | ||
| } | ||
| mTrackPtr->close(); | ||
| mTrackPtr.reset(); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| std::cerr << std::string("libdatachannel error while closing track: ") + ex.what() << std::endl; | ||
| return; | ||
| } | ||
| } | ||
| mOnOpenCallback.reset(); | ||
| mOnErrorCallback.reset(); | ||
| mOnMessageCallback.reset(); | ||
| mOnOpenCallback.reset(); | ||
| mOnErrorCallback.reset(); | ||
| mOnMessageCallback.reset(); | ||
| } | ||
@@ -106,22 +102,19 @@ | ||
| { | ||
| PLOG_DEBUG << "doCleanup() called"; | ||
| mOnClosedCallback.reset(); | ||
| instances.erase(this); | ||
| PLOG_DEBUG << "doCleanup() called"; | ||
| mOnClosedCallback.reset(); | ||
| instances.erase(this); | ||
| } | ||
| void TrackWrapper::close(const Napi::CallbackInfo &info) | ||
| { | ||
| doClose(); | ||
| } | ||
| void TrackWrapper::close(const Napi::CallbackInfo &info) { doClose(); } | ||
| Napi::Value TrackWrapper::direction(const Napi::CallbackInfo &info) | ||
| { | ||
| if (!mTrackPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "direction() called on destroyed track").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| if (!mTrackPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "direction() called on destroyed track").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| Napi::Env env = info.Env(); | ||
| return Napi::String::New(env, directionToStr(mTrackPtr->direction())); | ||
| Napi::Env env = info.Env(); | ||
| return Napi::String::New(env, directionToStr(mTrackPtr->direction())); | ||
| } | ||
@@ -131,10 +124,10 @@ | ||
| { | ||
| if (!mTrackPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "mid() called on destroyed track").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| if (!mTrackPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "mid() called on destroyed track").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| Napi::Env env = info.Env(); | ||
| return Napi::String::New(env, mTrackPtr->mid()); | ||
| Napi::Env env = info.Env(); | ||
| return Napi::String::New(env, mTrackPtr->mid()); | ||
| } | ||
@@ -144,10 +137,10 @@ | ||
| { | ||
| if (!mTrackPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "type() called on destroyed track").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| if (!mTrackPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "type() called on destroyed track").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| Napi::Env env = info.Env(); | ||
| return Napi::String::New(env, mTrackPtr->description().type()); | ||
| Napi::Env env = info.Env(); | ||
| return Napi::String::New(env, mTrackPtr->description().type()); | ||
| } | ||
@@ -157,27 +150,28 @@ | ||
| { | ||
| if (!mTrackPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "sendMessage() called on destroyed track").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| if (!mTrackPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "sendMessage() called on destroyed track").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| // Allow call with NULL | ||
| if (length < 1 || (!info[0].IsString() && !info[0].IsNull())) | ||
| { | ||
| Napi::TypeError::New(env, "String or Null expected").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| // Allow call with NULL | ||
| if (length < 1 || (!info[0].IsString() && !info[0].IsNull())) | ||
| { | ||
| Napi::TypeError::New(env, "String or Null expected").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| try | ||
| { | ||
| return Napi::Boolean::New(info.Env(), mTrackPtr->send(info[0].As<Napi::String>().ToString())); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error while sending track message: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Boolean::New(info.Env(), false); | ||
| } | ||
| try | ||
| { | ||
| return Napi::Boolean::New(info.Env(), mTrackPtr->send(info[0].As<Napi::String>().ToString())); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error while sending track message: ") + ex.what()) | ||
| .ThrowAsJavaScriptException(); | ||
| return Napi::Boolean::New(info.Env(), false); | ||
| } | ||
| } | ||
@@ -187,27 +181,28 @@ | ||
| { | ||
| if (!mTrackPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "sendMessageBinary() called on destroyed track").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| if (!mTrackPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "sendMessageBinary() called on destroyed track").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsBuffer()) | ||
| { | ||
| Napi::TypeError::New(env, "Buffer expected").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| if (length < 1 || !info[0].IsBuffer()) | ||
| { | ||
| Napi::TypeError::New(env, "Buffer expected").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| try | ||
| { | ||
| Napi::Uint8Array buffer = info[0].As<Napi::Uint8Array>(); | ||
| return Napi::Boolean::New(info.Env(), mTrackPtr->send((std::byte *)buffer.Data(), buffer.ByteLength())); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error while sending track message: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Boolean::New(info.Env(), false); | ||
| } | ||
| try | ||
| { | ||
| Napi::Uint8Array buffer = info[0].As<Napi::Uint8Array>(); | ||
| return Napi::Boolean::New(info.Env(), mTrackPtr->send((std::byte *)buffer.Data(), buffer.ByteLength())); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error while sending track message: ") + ex.what()) | ||
| .ThrowAsJavaScriptException(); | ||
| return Napi::Boolean::New(info.Env(), false); | ||
| } | ||
| } | ||
@@ -217,10 +212,10 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| Napi::Env env = info.Env(); | ||
| if (!mTrackPtr) | ||
| { | ||
| return Napi::Boolean::New(env, false); | ||
| } | ||
| if (!mTrackPtr) | ||
| { | ||
| return Napi::Boolean::New(env, false); | ||
| } | ||
| return Napi::Boolean::New(env, mTrackPtr->isOpen()); | ||
| return Napi::Boolean::New(env, mTrackPtr->isOpen()); | ||
| } | ||
@@ -230,10 +225,10 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| Napi::Env env = info.Env(); | ||
| if (!mTrackPtr) | ||
| { | ||
| return Napi::Boolean::New(env, true); | ||
| } | ||
| if (!mTrackPtr) | ||
| { | ||
| return Napi::Boolean::New(env, true); | ||
| } | ||
| return Napi::Boolean::New(env, mTrackPtr->isClosed()); | ||
| return Napi::Boolean::New(env, mTrackPtr->isClosed()); | ||
| } | ||
@@ -243,18 +238,18 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| Napi::Env env = info.Env(); | ||
| if (!mTrackPtr) | ||
| { | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| if (!mTrackPtr) | ||
| { | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| try | ||
| { | ||
| return Napi::Number::New(info.Env(), mTrackPtr->maxMessageSize()); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| try | ||
| { | ||
| return Napi::Number::New(info.Env(), mTrackPtr->maxMessageSize()); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| } | ||
@@ -264,19 +259,19 @@ | ||
| { | ||
| if (!mTrackPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "requestBitrate() called on destroyed track").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| if (!mTrackPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "requestBitrate() called on destroyed track").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "Number expected").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "Number expected").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| unsigned int bitrate = static_cast<uint32_t>(info[0].As<Napi::Number>()); | ||
| return Napi::Boolean::New(env, mTrackPtr->requestBitrate(bitrate)); | ||
| unsigned int bitrate = static_cast<uint32_t>(info[0].As<Napi::Number>()); | ||
| return Napi::Boolean::New(env, mTrackPtr->requestBitrate(bitrate)); | ||
| } | ||
@@ -286,10 +281,10 @@ | ||
| { | ||
| if (!mTrackPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "requestKeyframe() called on destroyed track").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| if (!mTrackPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "requestKeyframe() called on destroyed track").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| Napi::Env env = info.Env(); | ||
| return Napi::Boolean::New(env, mTrackPtr->requestKeyframe()); | ||
| Napi::Env env = info.Env(); | ||
| return Napi::Boolean::New(env, mTrackPtr->requestKeyframe()); | ||
| } | ||
@@ -299,19 +294,20 @@ | ||
| { | ||
| if (!mTrackPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "setMediaHandler() called on destroyed track").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (!mTrackPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "setMediaHandler() called on destroyed track").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsObject()) | ||
| { | ||
| Napi::TypeError::New(env, "MediaHandler class instance expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsObject()) | ||
| { | ||
| Napi::TypeError::New(env, "MediaHandler class instance expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| RtcpReceivingSessionWrapper *handler = Napi::ObjectWrap<RtcpReceivingSessionWrapper>::Unwrap(info[0].As<Napi::Object>()); | ||
| mTrackPtr->setMediaHandler(handler->getSessionInstance()); | ||
| RtcpReceivingSessionWrapper *handler = | ||
| Napi::ObjectWrap<RtcpReceivingSessionWrapper>::Unwrap(info[0].As<Napi::Object>()); | ||
| mTrackPtr->setMediaHandler(handler->getSessionInstance()); | ||
| } | ||
@@ -321,32 +317,36 @@ | ||
| { | ||
| if (!mTrackPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "onOpen() called on destroyed track").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (!mTrackPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "onOpen() called on destroyed track").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| // Callback | ||
| mOnOpenCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| // Callback | ||
| mOnOpenCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| mTrackPtr->onOpen([&]() | ||
| { | ||
| if (mOnOpenCallback) | ||
| mOnOpenCallback->call([this](Napi::Env env, std::vector<napi_value> &args) { | ||
| // Check the track is not closed | ||
| if(instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| mTrackPtr->onOpen( | ||
| [&]() | ||
| { | ||
| if (mOnOpenCallback) | ||
| mOnOpenCallback->call( | ||
| [this](Napi::Env env, std::vector<napi_value> &args) | ||
| { | ||
| // Check the track is not closed | ||
| if (instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| args = {}; | ||
| }); }); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| args = {}; | ||
| }); | ||
| }); | ||
| } | ||
@@ -356,32 +356,35 @@ | ||
| { | ||
| if (!mTrackPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "onClosed() called on destroyed track").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (!mTrackPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "onClosed() called on destroyed track").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| // Callback | ||
| mOnClosedCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| // Callback | ||
| mOnClosedCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| mTrackPtr->onClosed([&]() | ||
| { | ||
| if (mOnClosedCallback) | ||
| mOnClosedCallback->call([this](Napi::Env env, std::vector<napi_value> &args) { | ||
| // Do not check if the data channel has been closed here | ||
| mTrackPtr->onClosed( | ||
| [&]() | ||
| { | ||
| if (mOnClosedCallback) | ||
| mOnClosedCallback->call( | ||
| [this](Napi::Env env, std::vector<napi_value> &args) | ||
| { | ||
| // Do not check if the data channel has been closed here | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| args = {}; | ||
| },[this]{ | ||
| doCleanup(); | ||
| }); }); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| args = {}; | ||
| }, | ||
| [this] { doCleanup(); }); | ||
| }); | ||
| } | ||
@@ -391,32 +394,36 @@ | ||
| { | ||
| if (!mTrackPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "onError() called on destroyed track").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (!mTrackPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "onError() called on destroyed track").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| // Callback | ||
| mOnErrorCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| // Callback | ||
| mOnErrorCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| mTrackPtr->onError([&](const std::string &error) | ||
| { | ||
| if (mOnErrorCallback) | ||
| mOnErrorCallback->call([this, error](Napi::Env env, std::vector<napi_value> &args) { | ||
| // Check the track is not closed | ||
| if(instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| mTrackPtr->onError( | ||
| [&](const std::string &error) | ||
| { | ||
| if (mOnErrorCallback) | ||
| mOnErrorCallback->call( | ||
| [this, error](Napi::Env env, std::vector<napi_value> &args) | ||
| { | ||
| // Check the track is not closed | ||
| if (instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| args = {Napi::String::New(env, error)}; | ||
| }); }); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| args = {Napi::String::New(env, error)}; | ||
| }); | ||
| }); | ||
| } | ||
@@ -426,36 +433,40 @@ | ||
| { | ||
| if (!mTrackPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "onMessage() called on destroyed track").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (!mTrackPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "onMessage() called on destroyed track").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| // Callback | ||
| mOnMessageCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| // Callback | ||
| mOnMessageCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| mTrackPtr->onMessage([&](std::variant<rtc::binary, std::string> message) | ||
| { | ||
| if (mOnMessageCallback) | ||
| mOnMessageCallback->call([this, message = std::move(message)](Napi::Env env, std::vector<napi_value> &args) { | ||
| // Check the track is not closed | ||
| if(instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| mTrackPtr->onMessage( | ||
| [&](std::variant<rtc::binary, std::string> message) | ||
| { | ||
| if (mOnMessageCallback) | ||
| mOnMessageCallback->call( | ||
| [this, message = std::move(message)](Napi::Env env, std::vector<napi_value> &args) | ||
| { | ||
| // Check the track is not closed | ||
| if (instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| if (std::holds_alternative<rtc::binary>(message)) // Track will always receive messages as binary | ||
| { | ||
| auto bin = std::get<rtc::binary>(std::move(message)); | ||
| args = {Napi::Buffer<std::byte>::Copy(env, bin.data(), bin.size())}; | ||
| } | ||
| }); }); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| if (std::holds_alternative<rtc::binary>(message)) // Track will always receive messages as binary | ||
| { | ||
| auto bin = std::get<rtc::binary>(std::move(message)); | ||
| args = {Napi::Buffer<std::byte>::Copy(env, bin.data(), bin.size())}; | ||
| } | ||
| }); | ||
| }); | ||
| } |
| #ifndef MEDIA_TRACK_WRAPPER_H | ||
| #define MEDIA_TRACK_WRAPPER_H | ||
| #include <iostream> | ||
| #include <string> | ||
| #include <variant> | ||
| #include <memory> | ||
@@ -8,0 +5,0 @@ #include <unordered_set> |
+298
-299
@@ -9,42 +9,41 @@ #include "media-video-wrapper.h" | ||
| { | ||
| Napi::HandleScope scope(env); | ||
| Napi::HandleScope scope(env); | ||
| Napi::Function func = Napi::ObjectWrap<VideoWrapper>::DefineClass( | ||
| env, | ||
| "Video", | ||
| { | ||
| InstanceValue("media-type-video", Napi::Boolean::New(env, true)), | ||
| InstanceMethod("addH264Codec", &VideoWrapper::addH264Codec), | ||
| InstanceMethod("addVideoCodec", &VideoWrapper::addVideoCodec), | ||
| InstanceMethod("addVP8Codec", &VideoWrapper::addVP8Codec), | ||
| InstanceMethod("addVP9Codec", &VideoWrapper::addVP9Codec), | ||
| InstanceMethod("direction", &VideoWrapper::direction), | ||
| InstanceMethod("generateSdp", &VideoWrapper::generateSdp), | ||
| InstanceMethod("mid", &VideoWrapper::mid), | ||
| InstanceMethod("setDirection", &VideoWrapper::setDirection), | ||
| InstanceMethod("description", &VideoWrapper::description), | ||
| InstanceMethod("removeFormat", &VideoWrapper::removeFormat), | ||
| InstanceMethod("addSSRC", &VideoWrapper::addSSRC), | ||
| InstanceMethod("removeSSRC", &VideoWrapper::removeSSRC), | ||
| InstanceMethod("replaceSSRC", &VideoWrapper::replaceSSRC), | ||
| InstanceMethod("hasSSRC", &VideoWrapper::hasSSRC), | ||
| InstanceMethod("getSSRCs", &VideoWrapper::getSSRCs), | ||
| InstanceMethod("getCNameForSsrc", &VideoWrapper::getCNameForSsrc), | ||
| InstanceMethod("setBitrate", &VideoWrapper::setBitrate), | ||
| InstanceMethod("getBitrate", &VideoWrapper::getBitrate), | ||
| InstanceMethod("hasPayloadType", &VideoWrapper::hasPayloadType), | ||
| InstanceMethod("addRTXCodec", &VideoWrapper::addRTXCodec), | ||
| InstanceMethod("addRTPMap", &VideoWrapper::addRTPMap), | ||
| InstanceMethod("parseSdpLine", &VideoWrapper::parseSdpLine), | ||
| }); | ||
| Napi::Function func = | ||
| Napi::ObjectWrap<VideoWrapper>::DefineClass(env, "Video", | ||
| { | ||
| InstanceValue("media-type-video", Napi::Boolean::New(env, true)), | ||
| InstanceMethod("addH264Codec", &VideoWrapper::addH264Codec), | ||
| InstanceMethod("addVideoCodec", &VideoWrapper::addVideoCodec), | ||
| InstanceMethod("addVP8Codec", &VideoWrapper::addVP8Codec), | ||
| InstanceMethod("addVP9Codec", &VideoWrapper::addVP9Codec), | ||
| InstanceMethod("direction", &VideoWrapper::direction), | ||
| InstanceMethod("generateSdp", &VideoWrapper::generateSdp), | ||
| InstanceMethod("mid", &VideoWrapper::mid), | ||
| InstanceMethod("setDirection", &VideoWrapper::setDirection), | ||
| InstanceMethod("description", &VideoWrapper::description), | ||
| InstanceMethod("removeFormat", &VideoWrapper::removeFormat), | ||
| InstanceMethod("addSSRC", &VideoWrapper::addSSRC), | ||
| InstanceMethod("removeSSRC", &VideoWrapper::removeSSRC), | ||
| InstanceMethod("replaceSSRC", &VideoWrapper::replaceSSRC), | ||
| InstanceMethod("hasSSRC", &VideoWrapper::hasSSRC), | ||
| InstanceMethod("getSSRCs", &VideoWrapper::getSSRCs), | ||
| InstanceMethod("getCNameForSsrc", &VideoWrapper::getCNameForSsrc), | ||
| InstanceMethod("setBitrate", &VideoWrapper::setBitrate), | ||
| InstanceMethod("getBitrate", &VideoWrapper::getBitrate), | ||
| InstanceMethod("hasPayloadType", &VideoWrapper::hasPayloadType), | ||
| InstanceMethod("addRTXCodec", &VideoWrapper::addRTXCodec), | ||
| InstanceMethod("addRTPMap", &VideoWrapper::addRTPMap), | ||
| InstanceMethod("parseSdpLine", &VideoWrapper::parseSdpLine), | ||
| }); | ||
| // If this is not the first call, we don't want to reassign the constructor (hot-reload problem) | ||
| if(constructor.IsEmpty()) | ||
| { | ||
| constructor = Napi::Persistent(func); | ||
| constructor.SuppressDestruct(); | ||
| } | ||
| // If this is not the first call, we don't want to reassign the constructor (hot-reload problem) | ||
| if (constructor.IsEmpty()) | ||
| { | ||
| constructor = Napi::Persistent(func); | ||
| constructor.SuppressDestruct(); | ||
| } | ||
| exports.Set("Video", func); | ||
| return exports; | ||
| exports.Set("Video", func); | ||
| return exports; | ||
| } | ||
@@ -54,35 +53,35 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| std::string mid = "video"; | ||
| rtc::Description::Direction dir = rtc::Description::Direction::Unknown; | ||
| std::string mid = "video"; | ||
| rtc::Description::Direction dir = rtc::Description::Direction::Unknown; | ||
| // optional | ||
| if (length > 0) | ||
| // optional | ||
| if (length > 0) | ||
| { | ||
| if (!info[0].IsString()) | ||
| { | ||
| if (!info[0].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "mid (String) expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| mid = info[0].As<Napi::String>().ToString(); | ||
| Napi::TypeError::New(env, "mid (String) expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| mid = info[0].As<Napi::String>().ToString(); | ||
| } | ||
| // ootional | ||
| if (length > 1) | ||
| // ootional | ||
| if (length > 1) | ||
| { | ||
| if (!info[1].IsString()) | ||
| { | ||
| if (!info[1].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "direction (String) expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| std::string dirAsStr = info[1].As<Napi::String>().ToString(); | ||
| dir = strToDirection(dirAsStr); | ||
| Napi::TypeError::New(env, "direction (String) expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| mVideoPtr = std::make_unique<rtc::Description::Video>(mid, dir); | ||
| std::string dirAsStr = info[1].As<Napi::String>().ToString(); | ||
| dir = strToDirection(dirAsStr); | ||
| } | ||
| instances.insert(this); | ||
| mVideoPtr = std::make_unique<rtc::Description::Video>(mid, dir); | ||
| instances.insert(this); | ||
| } | ||
@@ -92,37 +91,34 @@ | ||
| { | ||
| mVideoPtr.reset(); | ||
| instances.erase(this); | ||
| mVideoPtr.reset(); | ||
| instances.erase(this); | ||
| } | ||
| rtc::Description::Video VideoWrapper::getVideoInstance() | ||
| { | ||
| return *(mVideoPtr.get()); | ||
| } | ||
| rtc::Description::Video VideoWrapper::getVideoInstance() { return *(mVideoPtr.get()); } | ||
| void VideoWrapper::addVideoCodec(const Napi::CallbackInfo &info) | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 2 || !info[0].IsNumber() || !info[1].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number, String, String[optional]) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 2 || !info[0].IsNumber() || !info[1].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number, String, String[optional]) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| int payloadType = info[0].As<Napi::Number>().ToNumber(); | ||
| std::string codec = info[1].As<Napi::String>().ToString(); | ||
| std::optional<std::string> profile = std::nullopt; | ||
| int payloadType = info[0].As<Napi::Number>().ToNumber(); | ||
| std::string codec = info[1].As<Napi::String>().ToString(); | ||
| std::optional<std::string> profile = std::nullopt; | ||
| if (length > 2) | ||
| if (length > 2) | ||
| { | ||
| if (!info[2].IsString()) | ||
| { | ||
| if (!info[2].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "profile (String) expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| profile = info[2].As<Napi::String>().ToString(); | ||
| Napi::TypeError::New(env, "profile (String) expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| profile = info[2].As<Napi::String>().ToString(); | ||
| } | ||
| mVideoPtr->addVideoCodec(payloadType, codec, profile); | ||
| mVideoPtr->addVideoCodec(payloadType, codec, profile); | ||
| } | ||
@@ -132,25 +128,25 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| int payloadType = info[0].As<Napi::Number>().ToNumber(); | ||
| std::string profile = rtc::DEFAULT_H264_VIDEO_PROFILE; | ||
| int payloadType = info[0].As<Napi::Number>().ToNumber(); | ||
| std::string profile = rtc::DEFAULT_H264_VIDEO_PROFILE; | ||
| if (length > 1) | ||
| if (length > 1) | ||
| { | ||
| if (!info[1].IsString()) | ||
| { | ||
| if (!info[1].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "profile (String) expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| profile = info[1].As<Napi::String>().ToString(); | ||
| Napi::TypeError::New(env, "profile (String) expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| profile = info[1].As<Napi::String>().ToString(); | ||
| } | ||
| mVideoPtr->addH264Codec(payloadType, profile); | ||
| mVideoPtr->addH264Codec(payloadType, profile); | ||
| } | ||
@@ -160,14 +156,14 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| int payloadType = info[0].As<Napi::Number>().ToNumber(); | ||
| int payloadType = info[0].As<Napi::Number>().ToNumber(); | ||
| mVideoPtr->addVP8Codec(payloadType); | ||
| mVideoPtr->addVP8Codec(payloadType); | ||
| } | ||
@@ -177,14 +173,14 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| int payloadType = info[0].As<Napi::Number>().ToNumber(); | ||
| int payloadType = info[0].As<Napi::Number>().ToNumber(); | ||
| mVideoPtr->addVP9Codec(payloadType); | ||
| mVideoPtr->addVP9Codec(payloadType); | ||
| } | ||
@@ -194,4 +190,4 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| return Napi::String::New(env, directionToStr(mVideoPtr->direction())); | ||
| Napi::Env env = info.Env(); | ||
| return Napi::String::New(env, directionToStr(mVideoPtr->direction())); | ||
| } | ||
@@ -201,16 +197,16 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 3 || !info[0].IsString() || !info[1].IsString() || !info[2].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (String, String, Number) as param").ThrowAsJavaScriptException(); | ||
| return Napi::String::New(env, ""); | ||
| } | ||
| if (length < 3 || !info[0].IsString() || !info[1].IsString() || !info[2].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (String, String, Number) as param").ThrowAsJavaScriptException(); | ||
| return Napi::String::New(env, ""); | ||
| } | ||
| std::string eol = info[0].As<Napi::String>().ToString(); | ||
| std::string addr = info[1].As<Napi::String>().ToString(); | ||
| uint16_t port = info[2].As<Napi::Number>().Uint32Value(); | ||
| std::string eol = info[0].As<Napi::String>().ToString(); | ||
| std::string addr = info[1].As<Napi::String>().ToString(); | ||
| uint16_t port = info[2].As<Napi::Number>().Uint32Value(); | ||
| return Napi::String::New(env, mVideoPtr->generateSdp(eol, addr, port)); | ||
| return Napi::String::New(env, mVideoPtr->generateSdp(eol, addr, port)); | ||
| } | ||
@@ -220,4 +216,4 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| return Napi::String::New(env, mVideoPtr->mid()); | ||
| Napi::Env env = info.Env(); | ||
| return Napi::String::New(env, mVideoPtr->mid()); | ||
| } | ||
@@ -227,14 +223,14 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (String) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (String) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| std::string dirAsStr = info[0].As<Napi::String>().ToString(); | ||
| rtc::Description::Direction dir = strToDirection(dirAsStr); | ||
| mVideoPtr->setDirection(dir); | ||
| std::string dirAsStr = info[0].As<Napi::String>().ToString(); | ||
| rtc::Description::Direction dir = strToDirection(dirAsStr); | ||
| mVideoPtr->setDirection(dir); | ||
| } | ||
@@ -244,4 +240,4 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| return Napi::String::New(env, mVideoPtr->description()); | ||
| Napi::Env env = info.Env(); | ||
| return Napi::String::New(env, mVideoPtr->description()); | ||
| } | ||
@@ -251,14 +247,14 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (String) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (String) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| std::string fmt = info[0].As<Napi::String>().ToString(); | ||
| std::string fmt = info[0].As<Napi::String>().ToString(); | ||
| mVideoPtr->removeFormat(fmt); | ||
| mVideoPtr->removeFormat(fmt); | ||
| } | ||
@@ -268,47 +264,48 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number, String[optional], String[optional], String[optional]) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number, String[optional], String[optional], String[optional]) as param") | ||
| .ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| uint32_t ssrc = static_cast<uint32_t>(info[0].As<Napi::Number>().ToNumber()); | ||
| std::optional<std::string> name; | ||
| std::optional<std::string> msid = std::nullopt; | ||
| std::optional<std::string> trackID = std::nullopt; | ||
| uint32_t ssrc = static_cast<uint32_t>(info[0].As<Napi::Number>().ToNumber()); | ||
| std::optional<std::string> name; | ||
| std::optional<std::string> msid = std::nullopt; | ||
| std::optional<std::string> trackID = std::nullopt; | ||
| if (length > 1) | ||
| if (length > 1) | ||
| { | ||
| if (!info[1].IsString()) | ||
| { | ||
| if (!info[1].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "name as String expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| name = info[1].As<Napi::String>().ToString(); | ||
| Napi::TypeError::New(env, "name as String expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| name = info[1].As<Napi::String>().ToString(); | ||
| } | ||
| if (length > 2) | ||
| if (length > 2) | ||
| { | ||
| if (!info[2].IsString()) | ||
| { | ||
| if (!info[2].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "msid as String expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| msid = info[2].As<Napi::String>().ToString(); | ||
| Napi::TypeError::New(env, "msid as String expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| msid = info[2].As<Napi::String>().ToString(); | ||
| } | ||
| if (length > 3) | ||
| if (length > 3) | ||
| { | ||
| if (!info[3].IsString()) | ||
| { | ||
| if (!info[3].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "trackID as String expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| trackID = info[3].As<Napi::String>().ToString(); | ||
| Napi::TypeError::New(env, "trackID as String expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| trackID = info[3].As<Napi::String>().ToString(); | ||
| } | ||
| mVideoPtr->addSSRC(ssrc, name, msid, trackID); | ||
| mVideoPtr->addSSRC(ssrc, name, msid, trackID); | ||
| } | ||
@@ -318,14 +315,14 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| uint32_t ssrc = static_cast<uint32_t>(info[0].As<Napi::Number>().ToNumber()); | ||
| uint32_t ssrc = static_cast<uint32_t>(info[0].As<Napi::Number>().ToNumber()); | ||
| mVideoPtr->removeSSRC(ssrc); | ||
| mVideoPtr->removeSSRC(ssrc); | ||
| } | ||
@@ -335,48 +332,50 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 2 || !info[0].IsNumber() || !info[1].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number, Number, String[optional], String[optional], String[optional]) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 2 || !info[0].IsNumber() || !info[1].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, | ||
| "We expect (Number, Number, String[optional], String[optional], String[optional]) as param") | ||
| .ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| uint32_t ssrc = static_cast<uint32_t>(info[0].As<Napi::Number>().ToNumber()); | ||
| uint32_t oldSsrc = static_cast<uint32_t>(info[1].As<Napi::Number>().ToNumber()); | ||
| std::optional<std::string> name; | ||
| std::optional<std::string> msid = std::nullopt; | ||
| std::optional<std::string> trackID = std::nullopt; | ||
| uint32_t ssrc = static_cast<uint32_t>(info[0].As<Napi::Number>().ToNumber()); | ||
| uint32_t oldSsrc = static_cast<uint32_t>(info[1].As<Napi::Number>().ToNumber()); | ||
| std::optional<std::string> name; | ||
| std::optional<std::string> msid = std::nullopt; | ||
| std::optional<std::string> trackID = std::nullopt; | ||
| if (length > 2) | ||
| if (length > 2) | ||
| { | ||
| if (!info[2].IsString()) | ||
| { | ||
| if (!info[2].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "name as String expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| name = info[2].As<Napi::String>().ToString(); | ||
| Napi::TypeError::New(env, "name as String expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| name = info[2].As<Napi::String>().ToString(); | ||
| } | ||
| if (length > 3) | ||
| if (length > 3) | ||
| { | ||
| if (!info[3].IsString()) | ||
| { | ||
| if (!info[3].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "msid as String expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| msid = info[3].As<Napi::String>().ToString(); | ||
| Napi::TypeError::New(env, "msid as String expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| msid = info[3].As<Napi::String>().ToString(); | ||
| } | ||
| if (length > 4) | ||
| if (length > 4) | ||
| { | ||
| if (!info[4].IsString()) | ||
| { | ||
| if (!info[4].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "trackID as String expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| trackID = info[4].As<Napi::String>().ToString(); | ||
| Napi::TypeError::New(env, "trackID as String expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| trackID = info[4].As<Napi::String>().ToString(); | ||
| } | ||
| mVideoPtr->replaceSSRC(oldSsrc, ssrc, name, msid, trackID); | ||
| mVideoPtr->replaceSSRC(oldSsrc, ssrc, name, msid, trackID); | ||
| } | ||
@@ -386,14 +385,14 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number) as param").ThrowAsJavaScriptException(); | ||
| return env.Null(); | ||
| } | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number) as param").ThrowAsJavaScriptException(); | ||
| return env.Null(); | ||
| } | ||
| uint32_t ssrc = static_cast<uint32_t>(info[0].As<Napi::Number>().ToNumber()); | ||
| uint32_t ssrc = static_cast<uint32_t>(info[0].As<Napi::Number>().ToNumber()); | ||
| return Napi::Boolean::New(env, mVideoPtr->hasSSRC(ssrc)); | ||
| return Napi::Boolean::New(env, mVideoPtr->hasSSRC(ssrc)); | ||
| } | ||
@@ -403,11 +402,11 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| Napi::Env env = info.Env(); | ||
| auto list = mVideoPtr->getSSRCs(); | ||
| auto list = mVideoPtr->getSSRCs(); | ||
| Napi::Uint32Array napiArr = Napi::Uint32Array::New(env, list.size()); | ||
| for (size_t i = 0; i < list.size(); i++) | ||
| napiArr[i] = list[i]; | ||
| Napi::Uint32Array napiArr = Napi::Uint32Array::New(env, list.size()); | ||
| for (size_t i = 0; i < list.size(); i++) | ||
| napiArr[i] = list[i]; | ||
| return napiArr; | ||
| return napiArr; | ||
| } | ||
@@ -417,19 +416,19 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number) as param").ThrowAsJavaScriptException(); | ||
| return env.Null(); | ||
| } | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number) as param").ThrowAsJavaScriptException(); | ||
| return env.Null(); | ||
| } | ||
| uint32_t ssrc = static_cast<uint32_t>(info[0].As<Napi::Number>().ToNumber()); | ||
| uint32_t ssrc = static_cast<uint32_t>(info[0].As<Napi::Number>().ToNumber()); | ||
| std::optional<std::string> name = mVideoPtr->getCNameForSsrc(ssrc); | ||
| std::optional<std::string> name = mVideoPtr->getCNameForSsrc(ssrc); | ||
| if (!name.has_value()) | ||
| return env.Null(); | ||
| if (!name.has_value()) | ||
| return env.Null(); | ||
| return Napi::String::New(env, name.value()); | ||
| return Napi::String::New(env, name.value()); | ||
| } | ||
@@ -439,13 +438,13 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| unsigned int bitrate = info[0].As<Napi::Number>().ToNumber().Uint32Value(); | ||
| mVideoPtr->setBitrate(bitrate); | ||
| unsigned int bitrate = info[0].As<Napi::Number>().ToNumber().Uint32Value(); | ||
| mVideoPtr->setBitrate(bitrate); | ||
| } | ||
@@ -455,3 +454,3 @@ | ||
| { | ||
| return Napi::Number::New(info.Env(), mVideoPtr->bitrate()); | ||
| return Napi::Number::New(info.Env(), mVideoPtr->bitrate()); | ||
| } | ||
@@ -461,14 +460,14 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number) as param").ThrowAsJavaScriptException(); | ||
| return env.Null(); | ||
| } | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number) as param").ThrowAsJavaScriptException(); | ||
| return env.Null(); | ||
| } | ||
| int payloadType = static_cast<int32_t>(info[0].As<Napi::Number>().ToNumber()); | ||
| int payloadType = static_cast<int32_t>(info[0].As<Napi::Number>().ToNumber()); | ||
| return Napi::Boolean::New(env, mVideoPtr->hasPayloadType(payloadType)); | ||
| return Napi::Boolean::New(env, mVideoPtr->hasPayloadType(payloadType)); | ||
| } | ||
@@ -478,16 +477,16 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 3 || !info[0].IsNumber() || !info[1].IsNumber() || !info[2].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number,Number,Number) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 3 || !info[0].IsNumber() || !info[1].IsNumber() || !info[2].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (Number,Number,Number) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| int payloadType = static_cast<int32_t>(info[0].As<Napi::Number>().ToNumber()); | ||
| int originalPayloadType = static_cast<int32_t>(info[1].As<Napi::Number>().ToNumber()); | ||
| unsigned int clockRate = static_cast<uint32_t>(info[2].As<Napi::Number>().ToNumber()); | ||
| int payloadType = static_cast<int32_t>(info[0].As<Napi::Number>().ToNumber()); | ||
| int originalPayloadType = static_cast<int32_t>(info[1].As<Napi::Number>().ToNumber()); | ||
| unsigned int clockRate = static_cast<uint32_t>(info[2].As<Napi::Number>().ToNumber()); | ||
| mVideoPtr->addRtxCodec(payloadType, originalPayloadType, clockRate); | ||
| mVideoPtr->addRtxCodec(payloadType, originalPayloadType, clockRate); | ||
| } | ||
@@ -497,3 +496,3 @@ | ||
| { | ||
| // mVideoPtr->addRTPMap() | ||
| // mVideoPtr->addRTPMap() | ||
| } | ||
@@ -503,14 +502,14 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (String) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "We expect (String) as param").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| std::string line = info[0].As<Napi::String>().ToString(); | ||
| std::string line = info[0].As<Napi::String>().ToString(); | ||
| mVideoPtr->parseSdpLine(line); | ||
| mVideoPtr->parseSdpLine(line); | ||
| } |
@@ -9,4 +9,2 @@ #ifndef MEDIA_VIDEO_WRAPPER_H | ||
| #include "thread-safe-callback.h" | ||
| class VideoWrapper : public Napi::ObjectWrap<VideoWrapper> | ||
@@ -57,2 +55,2 @@ { | ||
| #endif // MEDIA_VIDEO_WRAPPER_H | ||
| #endif // MEDIA_VIDEO_WRAPPER_H |
@@ -20,6 +20,6 @@ #include "peer-connection-wrapper.h" | ||
| { | ||
| PLOG_DEBUG << "CloseAll() called"; | ||
| auto copy(instances); | ||
| for (auto inst : copy) | ||
| inst->doClose(); | ||
| PLOG_DEBUG << "CloseAll() called"; | ||
| auto copy(instances); | ||
| for (auto inst : copy) | ||
| inst->doClose(); | ||
| } | ||
@@ -29,6 +29,6 @@ | ||
| { | ||
| PLOG_DEBUG << "CleanupAll() called"; | ||
| auto copy(instances); | ||
| for (auto inst : copy) | ||
| inst->doCleanup(); | ||
| PLOG_DEBUG << "CleanupAll() called"; | ||
| auto copy(instances); | ||
| for (auto inst : copy) | ||
| inst->doCleanup(); | ||
| } | ||
@@ -38,260 +38,265 @@ | ||
| { | ||
| Napi::HandleScope scope(env); | ||
| Napi::HandleScope scope(env); | ||
| Napi::Function func = DefineClass( | ||
| env, | ||
| "PeerConnection", | ||
| { | ||
| InstanceMethod("close", &PeerConnectionWrapper::close), | ||
| InstanceMethod("setLocalDescription", &PeerConnectionWrapper::setLocalDescription), | ||
| InstanceMethod("setRemoteDescription", &PeerConnectionWrapper::setRemoteDescription), | ||
| InstanceMethod("localDescription", &PeerConnectionWrapper::localDescription), | ||
| InstanceMethod("remoteDescription", &PeerConnectionWrapper::remoteDescription), | ||
| InstanceMethod("remoteFingerprint", &PeerConnectionWrapper::remoteFingerprint), | ||
| InstanceMethod("addRemoteCandidate", &PeerConnectionWrapper::addRemoteCandidate), | ||
| InstanceMethod("createDataChannel", &PeerConnectionWrapper::createDataChannel), | ||
| Napi::Function func = DefineClass(env, "PeerConnection", { | ||
| InstanceMethod("close", &PeerConnectionWrapper::close), | ||
| InstanceMethod("setLocalDescription", &PeerConnectionWrapper::setLocalDescription), | ||
| InstanceMethod("setRemoteDescription", &PeerConnectionWrapper::setRemoteDescription), | ||
| InstanceMethod("localDescription", &PeerConnectionWrapper::localDescription), | ||
| InstanceMethod("remoteDescription", &PeerConnectionWrapper::remoteDescription), | ||
| InstanceMethod("remoteFingerprint", &PeerConnectionWrapper::remoteFingerprint), | ||
| InstanceMethod("addRemoteCandidate", &PeerConnectionWrapper::addRemoteCandidate), | ||
| InstanceMethod("createDataChannel", &PeerConnectionWrapper::createDataChannel), | ||
| #if RTC_ENABLE_MEDIA == 1 | ||
| InstanceMethod("addTrack", &PeerConnectionWrapper::addTrack), | ||
| InstanceMethod("onTrack", &PeerConnectionWrapper::onTrack), | ||
| InstanceMethod("addTrack", &PeerConnectionWrapper::addTrack), | ||
| InstanceMethod("onTrack", &PeerConnectionWrapper::onTrack), | ||
| #endif | ||
| InstanceMethod("hasMedia", &PeerConnectionWrapper::hasMedia), | ||
| InstanceMethod("state", &PeerConnectionWrapper::state), | ||
| InstanceMethod("iceState", &PeerConnectionWrapper::iceState), | ||
| InstanceMethod("signalingState", &PeerConnectionWrapper::signalingState), | ||
| InstanceMethod("gatheringState", &PeerConnectionWrapper::gatheringState), | ||
| InstanceMethod("onLocalDescription", &PeerConnectionWrapper::onLocalDescription), | ||
| InstanceMethod("onLocalCandidate", &PeerConnectionWrapper::onLocalCandidate), | ||
| InstanceMethod("onStateChange", &PeerConnectionWrapper::onStateChange), | ||
| InstanceMethod("onIceStateChange", &PeerConnectionWrapper::onIceStateChange), | ||
| InstanceMethod("onSignalingStateChange", &PeerConnectionWrapper::onSignalingStateChange), | ||
| InstanceMethod("onGatheringStateChange", &PeerConnectionWrapper::onGatheringStateChange), | ||
| InstanceMethod("onDataChannel", &PeerConnectionWrapper::onDataChannel), | ||
| InstanceMethod("bytesSent", &PeerConnectionWrapper::bytesSent), | ||
| InstanceMethod("bytesReceived", &PeerConnectionWrapper::bytesReceived), | ||
| InstanceMethod("rtt", &PeerConnectionWrapper::rtt), | ||
| InstanceMethod("getSelectedCandidatePair", &PeerConnectionWrapper::getSelectedCandidatePair), | ||
| InstanceMethod("maxDataChannelId", &PeerConnectionWrapper::maxDataChannelId), | ||
| InstanceMethod("maxMessageSize", &PeerConnectionWrapper::maxMessageSize), | ||
| }); | ||
| InstanceMethod("hasMedia", &PeerConnectionWrapper::hasMedia), | ||
| InstanceMethod("state", &PeerConnectionWrapper::state), | ||
| InstanceMethod("iceState", &PeerConnectionWrapper::iceState), | ||
| InstanceMethod("signalingState", &PeerConnectionWrapper::signalingState), | ||
| InstanceMethod("gatheringState", &PeerConnectionWrapper::gatheringState), | ||
| InstanceMethod("onLocalDescription", &PeerConnectionWrapper::onLocalDescription), | ||
| InstanceMethod("onLocalCandidate", &PeerConnectionWrapper::onLocalCandidate), | ||
| InstanceMethod("onStateChange", &PeerConnectionWrapper::onStateChange), | ||
| InstanceMethod("onIceStateChange", &PeerConnectionWrapper::onIceStateChange), | ||
| InstanceMethod("onSignalingStateChange", &PeerConnectionWrapper::onSignalingStateChange), | ||
| InstanceMethod("onGatheringStateChange", &PeerConnectionWrapper::onGatheringStateChange), | ||
| InstanceMethod("onDataChannel", &PeerConnectionWrapper::onDataChannel), | ||
| InstanceMethod("bytesSent", &PeerConnectionWrapper::bytesSent), | ||
| InstanceMethod("bytesReceived", &PeerConnectionWrapper::bytesReceived), | ||
| InstanceMethod("rtt", &PeerConnectionWrapper::rtt), | ||
| InstanceMethod("getSelectedCandidatePair", &PeerConnectionWrapper::getSelectedCandidatePair), | ||
| InstanceMethod("maxDataChannelId", &PeerConnectionWrapper::maxDataChannelId), | ||
| InstanceMethod("maxMessageSize", &PeerConnectionWrapper::maxMessageSize), | ||
| }); | ||
| // If this is not the first call, we don't want to reassign the constructor (hot-reload problem) | ||
| if(constructor.IsEmpty()) | ||
| { | ||
| constructor = Napi::Persistent(func); | ||
| constructor.SuppressDestruct(); | ||
| } | ||
| // If this is not the first call, we don't want to reassign the constructor (hot-reload problem) | ||
| if (constructor.IsEmpty()) | ||
| { | ||
| constructor = Napi::Persistent(func); | ||
| constructor.SuppressDestruct(); | ||
| } | ||
| exports.Set("PeerConnection", func); | ||
| return exports; | ||
| exports.Set("PeerConnection", func); | ||
| return exports; | ||
| } | ||
| PeerConnectionWrapper::PeerConnectionWrapper(const Napi::CallbackInfo &info) : Napi::ObjectWrap<PeerConnectionWrapper>(info) | ||
| PeerConnectionWrapper::PeerConnectionWrapper(const Napi::CallbackInfo &info) | ||
| : Napi::ObjectWrap<PeerConnectionWrapper>(info) | ||
| { | ||
| PLOG_DEBUG << "Constructor called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| PLOG_DEBUG << "Constructor called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| // We expect (String, Object, Function) as param | ||
| if (length < 2 || !info[0].IsString() || !info[1].IsObject()) | ||
| { | ||
| Napi::TypeError::New(env, "Peer Name (String) and Configuration (Object) expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| // We expect (String, Object, Function) as param | ||
| if (length < 2 || !info[0].IsString() || !info[1].IsObject()) | ||
| { | ||
| Napi::TypeError::New(env, "Peer Name (String) and Configuration (Object) expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| // Peer Name | ||
| mPeerName = info[0].As<Napi::String>().ToString(); | ||
| // Peer Name | ||
| mPeerName = info[0].As<Napi::String>().ToString(); | ||
| // Peer Config | ||
| rtc::Configuration rtcConfig; | ||
| Napi::Object config = info[1].As<Napi::Object>(); | ||
| if (!config.Get("iceServers").IsArray()) | ||
| // Peer Config | ||
| rtc::Configuration rtcConfig; | ||
| Napi::Object config = info[1].As<Napi::Object>(); | ||
| if (!config.Get("iceServers").IsArray()) | ||
| { | ||
| Napi::TypeError::New(env, "iceServers(Array) expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| Napi::Array iceServers = config.Get("iceServers").As<Napi::Array>(); | ||
| for (uint32_t i = 0; i < iceServers.Length(); i++) | ||
| { | ||
| if (iceServers.Get(i).IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "iceServers(Array) expected").ThrowAsJavaScriptException(); | ||
| try | ||
| { | ||
| rtcConfig.iceServers.emplace_back(iceServers.Get(i).As<Napi::String>().ToString()); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::TypeError::New(env, "SyntaxError: IceServer config error: " + std::string(ex.what())) | ||
| .ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| } | ||
| Napi::Array iceServers = config.Get("iceServers").As<Napi::Array>(); | ||
| for (uint32_t i = 0; i < iceServers.Length(); i++) | ||
| else | ||
| { | ||
| if (iceServers.Get(i).IsString()){ | ||
| try | ||
| { | ||
| rtcConfig.iceServers.emplace_back(iceServers.Get(i).As<Napi::String>().ToString()); | ||
| } | ||
| catch(std::exception &ex) | ||
| { | ||
| Napi::TypeError::New(env, "SyntaxError: IceServer config error: " + std::string(ex.what())).ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| } | ||
| else | ||
| { | ||
| if (!iceServers.Get(i).IsObject()) | ||
| { | ||
| Napi::TypeError::New(env, "IceServer config should be a string Or an object").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (!iceServers.Get(i).IsObject()) | ||
| { | ||
| Napi::TypeError::New(env, "IceServer config should be a string Or an object").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| Napi::Object iceServer = iceServers.Get(i).As<Napi::Object>(); | ||
| if (!iceServer.Get("hostname").IsString() || !iceServer.Get("port").IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "IceServer config error (hostname OR/AND port is not suitable)").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (iceServer.Get("relayType").IsString() && | ||
| (!iceServer.Get("username").IsString() || !iceServer.Get("password").IsString())) | ||
| { | ||
| Napi::TypeError::New(env, "IceServer config error (username AND password is needed)").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| Napi::Object iceServer = iceServers.Get(i).As<Napi::Object>(); | ||
| if (!iceServer.Get("hostname").IsString() || !iceServer.Get("port").IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "IceServer config error (hostname OR/AND port is not suitable)") | ||
| .ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (iceServer.Get("relayType").IsString() && | ||
| (!iceServer.Get("username").IsString() || !iceServer.Get("password").IsString())) | ||
| { | ||
| Napi::TypeError::New(env, "IceServer config error (username AND password is needed)") | ||
| .ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (iceServer.Get("relayType").IsString()) | ||
| { | ||
| std::string relayTypeStr = iceServer.Get("relayType").As<Napi::String>(); | ||
| rtc::IceServer::RelayType relayType = rtc::IceServer::RelayType::TurnUdp; | ||
| if (relayTypeStr.compare("TurnTcp") == 0) | ||
| relayType = rtc::IceServer::RelayType::TurnTcp; | ||
| if (relayTypeStr.compare("TurnTls") == 0) | ||
| relayType = rtc::IceServer::RelayType::TurnTls; | ||
| if (iceServer.Get("relayType").IsString()) | ||
| { | ||
| std::string relayTypeStr = iceServer.Get("relayType").As<Napi::String>(); | ||
| rtc::IceServer::RelayType relayType = rtc::IceServer::RelayType::TurnUdp; | ||
| if (relayTypeStr.compare("TurnTcp") == 0) | ||
| relayType = rtc::IceServer::RelayType::TurnTcp; | ||
| if (relayTypeStr.compare("TurnTls") == 0) | ||
| relayType = rtc::IceServer::RelayType::TurnTls; | ||
| rtcConfig.iceServers.emplace_back( | ||
| rtc::IceServer(iceServer.Get("hostname").As<Napi::String>(), | ||
| uint16_t(iceServer.Get("port").As<Napi::Number>().Uint32Value()), | ||
| iceServer.Get("username").As<Napi::String>(), | ||
| iceServer.Get("password").As<Napi::String>(), | ||
| relayType)); | ||
| } | ||
| else | ||
| { | ||
| rtcConfig.iceServers.emplace_back( | ||
| rtc::IceServer( | ||
| iceServer.Get("hostname").As<Napi::String>(), | ||
| uint16_t(iceServer.Get("port").As<Napi::Number>().Uint32Value()))); | ||
| } | ||
| } | ||
| rtcConfig.iceServers.emplace_back(rtc::IceServer( | ||
| iceServer.Get("hostname").As<Napi::String>(), | ||
| uint16_t(iceServer.Get("port").As<Napi::Number>().Uint32Value()), | ||
| iceServer.Get("username").As<Napi::String>(), iceServer.Get("password").As<Napi::String>(), relayType)); | ||
| } | ||
| else | ||
| { | ||
| rtcConfig.iceServers.emplace_back( | ||
| rtc::IceServer(iceServer.Get("hostname").As<Napi::String>(), | ||
| uint16_t(iceServer.Get("port").As<Napi::Number>().Uint32Value()))); | ||
| } | ||
| } | ||
| } | ||
| // Proxy Server | ||
| if (config.Get("proxyServer").IsObject()) | ||
| { | ||
| Napi::Object proxyServer = config.Get("proxyServer").As<Napi::Object>(); | ||
| // Proxy Server | ||
| if (config.Get("proxyServer").IsObject()) | ||
| { | ||
| Napi::Object proxyServer = config.Get("proxyServer").As<Napi::Object>(); | ||
| // IP | ||
| std::string ip = proxyServer.Get("ip").As<Napi::String>(); | ||
| // IP | ||
| std::string ip = proxyServer.Get("ip").As<Napi::String>(); | ||
| // Port | ||
| uint16_t port = proxyServer.Get("port").As<Napi::Number>().Uint32Value(); | ||
| // Port | ||
| uint16_t port = proxyServer.Get("port").As<Napi::Number>().Uint32Value(); | ||
| // Type | ||
| std::string strType = proxyServer.Get("type").As<Napi::String>().ToString(); | ||
| rtc::ProxyServer::Type type = rtc::ProxyServer::Type::Http; | ||
| // Type | ||
| std::string strType = proxyServer.Get("type").As<Napi::String>().ToString(); | ||
| rtc::ProxyServer::Type type = rtc::ProxyServer::Type::Http; | ||
| if (strType == "Socks5") | ||
| type = rtc::ProxyServer::Type::Socks5; | ||
| if (strType == "Socks5") | ||
| type = rtc::ProxyServer::Type::Socks5; | ||
| // Username & Password | ||
| std::string username = ""; | ||
| std::string password = ""; | ||
| // Username & Password | ||
| std::string username = ""; | ||
| std::string password = ""; | ||
| if (proxyServer.Get("username").IsString()) | ||
| username = proxyServer.Get("username").As<Napi::String>().ToString(); | ||
| if (proxyServer.Get("password").IsString()) | ||
| username = proxyServer.Get("password").As<Napi::String>().ToString(); | ||
| if (proxyServer.Get("username").IsString()) | ||
| username = proxyServer.Get("username").As<Napi::String>().ToString(); | ||
| if (proxyServer.Get("password").IsString()) | ||
| password = proxyServer.Get("password").As<Napi::String>().ToString(); | ||
| rtcConfig.proxyServer = rtc::ProxyServer(type, ip, port, username, password); | ||
| } | ||
| rtcConfig.proxyServer = rtc::ProxyServer(type, ip, port, username, password); | ||
| } | ||
| // bind address, libjuice only | ||
| if (config.Get("bindAddress").IsString()) | ||
| rtcConfig.bindAddress = config.Get("bindAddress").As<Napi::String>().ToString(); | ||
| // bind address, libjuice only | ||
| if (config.Get("bindAddress").IsString()) | ||
| rtcConfig.bindAddress = config.Get("bindAddress").As<Napi::String>().ToString(); | ||
| // Port Ranges | ||
| if (config.Get("portRangeBegin").IsNumber()) | ||
| rtcConfig.portRangeBegin = config.Get("portRangeBegin").As<Napi::Number>().Uint32Value(); | ||
| if (config.Get("portRangeEnd").IsNumber()) | ||
| rtcConfig.portRangeEnd = config.Get("portRangeEnd").As<Napi::Number>().Uint32Value(); | ||
| // Port Ranges | ||
| if (config.Get("portRangeBegin").IsNumber()) | ||
| rtcConfig.portRangeBegin = config.Get("portRangeBegin").As<Napi::Number>().Uint32Value(); | ||
| if (config.Get("portRangeEnd").IsNumber()) | ||
| rtcConfig.portRangeEnd = config.Get("portRangeEnd").As<Napi::Number>().Uint32Value(); | ||
| // enableIceTcp option | ||
| if (config.Get("enableIceTcp").IsBoolean()) | ||
| rtcConfig.enableIceTcp = config.Get("enableIceTcp").As<Napi::Boolean>(); | ||
| // enableIceTcp option | ||
| if (config.Get("enableIceTcp").IsBoolean()) | ||
| rtcConfig.enableIceTcp = config.Get("enableIceTcp").As<Napi::Boolean>(); | ||
| // enableIceUdpMux option | ||
| if (config.Get("enableIceUdpMux").IsBoolean()) | ||
| rtcConfig.enableIceUdpMux = config.Get("enableIceUdpMux").As<Napi::Boolean>(); | ||
| // enableIceUdpMux option | ||
| if (config.Get("enableIceUdpMux").IsBoolean()) | ||
| rtcConfig.enableIceUdpMux = config.Get("enableIceUdpMux").As<Napi::Boolean>(); | ||
| // disableAutoNegotiation option | ||
| if (config.Get("disableAutoNegotiation").IsBoolean()) | ||
| rtcConfig.disableAutoNegotiation = config.Get("disableAutoNegotiation").As<Napi::Boolean>(); | ||
| // disableAutoNegotiation option | ||
| if (config.Get("disableAutoNegotiation").IsBoolean()) | ||
| rtcConfig.disableAutoNegotiation = config.Get("disableAutoNegotiation").As<Napi::Boolean>(); | ||
| // disableAutoGathering option | ||
| if (config.Get("disableAutoGathering").IsBoolean()) | ||
| rtcConfig.disableAutoGathering = config.Get("disableAutoGathering").As<Napi::Boolean>(); | ||
| // disableAutoGathering option | ||
| if (config.Get("disableAutoGathering").IsBoolean()) | ||
| rtcConfig.disableAutoGathering = config.Get("disableAutoGathering").As<Napi::Boolean>(); | ||
| // forceMediaTransport option | ||
| if (config.Get("forceMediaTransport").IsBoolean()) | ||
| rtcConfig.forceMediaTransport = config.Get("forceMediaTransport").As<Napi::Boolean>(); | ||
| // forceMediaTransport option | ||
| if (config.Get("forceMediaTransport").IsBoolean()) | ||
| rtcConfig.forceMediaTransport = config.Get("forceMediaTransport").As<Napi::Boolean>(); | ||
| // Max Message Size | ||
| if (config.Get("maxMessageSize").IsNumber()) | ||
| rtcConfig.maxMessageSize = config.Get("maxMessageSize").As<Napi::Number>().Int32Value(); | ||
| // Max Message Size | ||
| if (config.Get("maxMessageSize").IsNumber()) | ||
| rtcConfig.maxMessageSize = config.Get("maxMessageSize").As<Napi::Number>().Int32Value(); | ||
| // MTU | ||
| if (config.Get("mtu").IsNumber()) | ||
| rtcConfig.mtu = config.Get("mtu").As<Napi::Number>().Int32Value(); | ||
| // MTU | ||
| if (config.Get("mtu").IsNumber()) | ||
| rtcConfig.mtu = config.Get("mtu").As<Napi::Number>().Int32Value(); | ||
| // ICE transport policy | ||
| if (!config.Get("iceTransportPolicy").IsUndefined()) | ||
| // ICE transport policy | ||
| if (!config.Get("iceTransportPolicy").IsUndefined()) | ||
| { | ||
| if (!config.Get("iceTransportPolicy").IsString()) | ||
| { | ||
| if (!config.Get("iceTransportPolicy").IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "Invalid ICE transport policy, expected string").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| std::string strPolicy = config.Get("iceTransportPolicy").As<Napi::String>().ToString(); | ||
| if (strPolicy == "all") | ||
| rtcConfig.iceTransportPolicy = rtc::TransportPolicy::All; | ||
| else if (strPolicy == "relay") | ||
| rtcConfig.iceTransportPolicy = rtc::TransportPolicy::Relay; | ||
| else | ||
| { | ||
| Napi::TypeError::New(env, "Unknown ICE transport policy").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| Napi::TypeError::New(env, "Invalid ICE transport policy, expected string").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| // Allow skipping fingerprint validation | ||
| if (config.Get("disableFingerprintVerification").IsBoolean()) { | ||
| rtcConfig.disableFingerprintVerification = config.Get("disableFingerprintVerification").As<Napi::Boolean>(); | ||
| std::string strPolicy = config.Get("iceTransportPolicy").As<Napi::String>().ToString(); | ||
| if (strPolicy == "all") | ||
| rtcConfig.iceTransportPolicy = rtc::TransportPolicy::All; | ||
| else if (strPolicy == "relay") | ||
| rtcConfig.iceTransportPolicy = rtc::TransportPolicy::Relay; | ||
| else | ||
| { | ||
| Napi::TypeError::New(env, "Unknown ICE transport policy").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| } | ||
| // Specify certificate to use if set | ||
| if (config.Get("certificatePemFile").IsString()) { | ||
| rtcConfig.certificatePemFile = config.Get("certificatePemFile").As<Napi::String>().ToString(); | ||
| } | ||
| if (config.Get("keyPemFile").IsString()) { | ||
| rtcConfig.keyPemFile = config.Get("keyPemFile").As<Napi::String>().ToString(); | ||
| } | ||
| if (config.Get("keyPemPass").IsString()) { | ||
| rtcConfig.keyPemPass = config.Get("keyPemPass").As<Napi::String>().ToString(); | ||
| } | ||
| // Allow skipping fingerprint validation | ||
| if (config.Get("disableFingerprintVerification").IsBoolean()) | ||
| { | ||
| rtcConfig.disableFingerprintVerification = config.Get("disableFingerprintVerification").As<Napi::Boolean>(); | ||
| } | ||
| // Create peer-connection | ||
| try | ||
| { | ||
| PLOG_DEBUG << "Creating a new Peer Connection"; | ||
| mRtcPeerConnPtr = std::make_unique<rtc::PeerConnection>(rtcConfig); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error while creating peer connection: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| // Specify certificate to use if set | ||
| if (config.Get("certificatePemFile").IsString()) | ||
| { | ||
| rtcConfig.certificatePemFile = config.Get("certificatePemFile").As<Napi::String>().ToString(); | ||
| } | ||
| if (config.Get("keyPemFile").IsString()) | ||
| { | ||
| rtcConfig.keyPemFile = config.Get("keyPemFile").As<Napi::String>().ToString(); | ||
| } | ||
| if (config.Get("keyPemPass").IsString()) | ||
| { | ||
| rtcConfig.keyPemPass = config.Get("keyPemPass").As<Napi::String>().ToString(); | ||
| } | ||
| PLOG_DEBUG << "Peer Connection created"; | ||
| // Create peer-connection | ||
| try | ||
| { | ||
| PLOG_DEBUG << "Creating a new Peer Connection"; | ||
| mRtcPeerConnPtr = std::make_unique<rtc::PeerConnection>(rtcConfig); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error while creating peer connection: ") + ex.what()) | ||
| .ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| // State change callback must be set to trigger cleanup on close | ||
| mOnStateChangeCallback = std::make_unique<ThreadSafeCallback>(Napi::Function::New(info.Env(), [](const Napi::CallbackInfo &) {})); | ||
| PLOG_DEBUG << "Peer Connection created"; | ||
| instances.insert(this); | ||
| // State change callback must be set to trigger cleanup on close | ||
| mOnStateChangeCallback = | ||
| std::make_unique<ThreadSafeCallback>(Napi::Function::New(info.Env(), [](const Napi::CallbackInfo &) {})); | ||
| instances.insert(this); | ||
| } | ||
@@ -301,5 +306,5 @@ | ||
| { | ||
| PLOG_DEBUG << "Destructor called"; | ||
| doCleanup(); | ||
| doClose(); | ||
| PLOG_DEBUG << "Destructor called"; | ||
| doCleanup(); | ||
| doClose(); | ||
| } | ||
@@ -309,25 +314,25 @@ | ||
| { | ||
| PLOG_DEBUG << "doClose() called"; | ||
| if (mRtcPeerConnPtr) | ||
| PLOG_DEBUG << "doClose() called"; | ||
| if (mRtcPeerConnPtr) | ||
| { | ||
| PLOG_DEBUG << "Closing..."; | ||
| try | ||
| { | ||
| PLOG_DEBUG << "Closing..."; | ||
| try | ||
| { | ||
| mRtcPeerConnPtr->close(); | ||
| mRtcPeerConnPtr.reset(); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| std::cerr << std::string("libdatachannel error while closing peer connection: ") + ex.what() << std::endl; | ||
| return; | ||
| } | ||
| mRtcPeerConnPtr->close(); | ||
| mRtcPeerConnPtr.reset(); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| std::cerr << std::string("libdatachannel error while closing peer connection: ") + ex.what() << std::endl; | ||
| return; | ||
| } | ||
| } | ||
| mOnLocalDescriptionCallback.reset(); | ||
| mOnLocalCandidateCallback.reset(); | ||
| mOnIceStateChangeCallback.reset(); | ||
| mOnSignalingStateChangeCallback.reset(); | ||
| mOnGatheringStateChangeCallback.reset(); | ||
| mOnDataChannelCallback.reset(); | ||
| mOnTrackCallback.reset(); | ||
| mOnLocalDescriptionCallback.reset(); | ||
| mOnLocalCandidateCallback.reset(); | ||
| mOnIceStateChangeCallback.reset(); | ||
| mOnSignalingStateChangeCallback.reset(); | ||
| mOnGatheringStateChangeCallback.reset(); | ||
| mOnDataChannelCallback.reset(); | ||
| mOnTrackCallback.reset(); | ||
| } | ||
@@ -337,4 +342,4 @@ | ||
| { | ||
| PLOG_DEBUG << "close() called"; | ||
| doClose(); | ||
| PLOG_DEBUG << "close() called"; | ||
| doClose(); | ||
| } | ||
@@ -344,5 +349,5 @@ | ||
| { | ||
| PLOG_DEBUG << "doCleanup() called"; | ||
| mOnStateChangeCallback.reset(); | ||
| instances.erase(this); | ||
| PLOG_DEBUG << "doCleanup() called"; | ||
| mOnStateChangeCallback.reset(); | ||
| instances.erase(this); | ||
| } | ||
@@ -352,62 +357,64 @@ | ||
| { | ||
| PLOG_DEBUG << "setLocalDescription() called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| PLOG_DEBUG << "setLocalDescription() called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (!mRtcPeerConnPtr) | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| Napi::Error::New(env, "setLocalDescription() called on destroyed peer connection").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| rtc::Description::Type type = rtc::Description::Type::Unspec; | ||
| rtc::LocalDescriptionInit init; | ||
| // optional | ||
| if (length > 0) | ||
| { | ||
| if (!info[0].IsString()) | ||
| { | ||
| Napi::Error::New(env, "setLocalDescription() called on destroyed peer connection").ThrowAsJavaScriptException(); | ||
| return; | ||
| Napi::TypeError::New(env, "type (String) expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| std::string typeStr = info[0].As<Napi::String>().ToString(); | ||
| rtc::Description::Type type = rtc::Description::Type::Unspec; | ||
| rtc::LocalDescriptionInit init; | ||
| // Accept uppercase first letter for backward compatibility | ||
| if (typeStr.size() > 0) | ||
| typeStr[0] = std::tolower(typeStr[0]); | ||
| // optional | ||
| if (length > 0) | ||
| { | ||
| if (!info[0].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "type (String) expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| std::string typeStr = info[0].As<Napi::String>().ToString(); | ||
| if (typeStr == "answer") | ||
| type = rtc::Description::Type::Answer; | ||
| else if (typeStr == "offer") | ||
| type = rtc::Description::Type::Offer; | ||
| else if (typeStr == "pranswer") | ||
| type = rtc::Description::Type::Pranswer; | ||
| else if (typeStr == "rollback") | ||
| type = rtc::Description::Type::Rollback; | ||
| } | ||
| // Accept uppercase first letter for backward compatibility | ||
| if (typeStr.size() > 0) | ||
| typeStr[0] = std::tolower(typeStr[0]); | ||
| // optional | ||
| if (length > 1) | ||
| { | ||
| PLOG_DEBUG << "setLocalDescription() called with LocalDescriptionInit"; | ||
| if (typeStr == "answer") | ||
| type = rtc::Description::Type::Answer; | ||
| else if (typeStr == "offer") | ||
| type = rtc::Description::Type::Offer; | ||
| else if (typeStr == "pranswer") | ||
| type = rtc::Description::Type::Pranswer; | ||
| else if (typeStr == "rollback") | ||
| type = rtc::Description::Type::Rollback; | ||
| } | ||
| // optional | ||
| if (length > 1) | ||
| if (info[1].IsObject()) | ||
| { | ||
| PLOG_DEBUG << "setLocalDescription() called with LocalDescriptionInit"; | ||
| PLOG_DEBUG << "setLocalDescription() called with LocalDescriptionInit as object"; | ||
| Napi::Object obj = info[1].As<Napi::Object>(); | ||
| if (info[1].IsObject()) | ||
| { | ||
| PLOG_DEBUG << "setLocalDescription() called with LocalDescriptionInit as object"; | ||
| Napi::Object obj = info[1].As<Napi::Object>(); | ||
| if (obj.Get("iceUfrag").IsString()) | ||
| { | ||
| PLOG_DEBUG << "setLocalDescription() has ufrag"; | ||
| init.iceUfrag = obj.Get("iceUfrag").As<Napi::String>(); | ||
| } | ||
| if (obj.Get("iceUfrag").IsString()) { | ||
| PLOG_DEBUG << "setLocalDescription() has ufrag"; | ||
| init.iceUfrag = obj.Get("iceUfrag").As<Napi::String>(); | ||
| } | ||
| if (obj.Get("icePwd").IsString()) { | ||
| PLOG_DEBUG << "setLocalDescription() has password"; | ||
| init.icePwd = obj.Get("icePwd").As<Napi::String>(); | ||
| } | ||
| } | ||
| if (obj.Get("icePwd").IsString()) | ||
| { | ||
| PLOG_DEBUG << "setLocalDescription() has password"; | ||
| init.icePwd = obj.Get("icePwd").As<Napi::String>(); | ||
| } | ||
| } | ||
| } | ||
| mRtcPeerConnPtr->setLocalDescription(type, init); | ||
| mRtcPeerConnPtr->setLocalDescription(type, init); | ||
| } | ||
@@ -417,31 +424,32 @@ | ||
| { | ||
| PLOG_DEBUG << "setRemoteDescription() called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| PLOG_DEBUG << "setRemoteDescription() called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| Napi::Error::New(env, "setRemoteDescription() called on destroyed peer connection").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| Napi::Error::New(env, "setRemoteDescription() called on destroyed peer connection").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 2 || !info[0].IsString() || !info[1].IsString()) | ||
| { | ||
| Napi::TypeError::New(info.Env(), "String,String expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 2 || !info[0].IsString() || !info[1].IsString()) | ||
| { | ||
| Napi::TypeError::New(info.Env(), "String,String expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| std::string sdp = info[0].As<Napi::String>().ToString(); | ||
| std::string type = info[1].As<Napi::String>().ToString(); | ||
| std::string sdp = info[0].As<Napi::String>().ToString(); | ||
| std::string type = info[1].As<Napi::String>().ToString(); | ||
| try | ||
| { | ||
| rtc::Description desc(sdp, type); | ||
| mRtcPeerConnPtr->setRemoteDescription(desc); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error while adding remote description: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| try | ||
| { | ||
| rtc::Description desc(sdp, type); | ||
| mRtcPeerConnPtr->setRemoteDescription(desc); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error while adding remote description: ") + ex.what()) | ||
| .ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| } | ||
@@ -451,17 +459,17 @@ | ||
| { | ||
| PLOG_DEBUG << "localDescription() called"; | ||
| Napi::Env env = info.Env(); | ||
| PLOG_DEBUG << "localDescription() called"; | ||
| Napi::Env env = info.Env(); | ||
| std::optional<rtc::Description> desc = mRtcPeerConnPtr ? mRtcPeerConnPtr->localDescription() : std::nullopt; | ||
| std::optional<rtc::Description> desc = mRtcPeerConnPtr ? mRtcPeerConnPtr->localDescription() : std::nullopt; | ||
| // Return JS null if no description | ||
| if (!desc.has_value()) | ||
| { | ||
| return env.Null(); | ||
| } | ||
| // Return JS null if no description | ||
| if (!desc.has_value()) | ||
| { | ||
| return env.Null(); | ||
| } | ||
| Napi::Object obj = Napi::Object::New(env); | ||
| obj.Set("type", desc->typeString()); | ||
| obj.Set("sdp", desc.value()); | ||
| return obj; | ||
| Napi::Object obj = Napi::Object::New(env); | ||
| obj.Set("type", desc->typeString()); | ||
| obj.Set("sdp", desc.value()); | ||
| return obj; | ||
| } | ||
@@ -471,16 +479,16 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| Napi::Env env = info.Env(); | ||
| std::optional<rtc::Description> desc = mRtcPeerConnPtr ? mRtcPeerConnPtr->remoteDescription() : std::nullopt; | ||
| std::optional<rtc::Description> desc = mRtcPeerConnPtr ? mRtcPeerConnPtr->remoteDescription() : std::nullopt; | ||
| // Return JS null if no description | ||
| if (!desc.has_value()) | ||
| { | ||
| return env.Null(); | ||
| } | ||
| // Return JS null if no description | ||
| if (!desc.has_value()) | ||
| { | ||
| return env.Null(); | ||
| } | ||
| Napi::Object obj = Napi::Object::New(env); | ||
| obj.Set("type", desc->typeString()); | ||
| obj.Set("sdp", desc.value()); | ||
| return obj; | ||
| Napi::Object obj = Napi::Object::New(env); | ||
| obj.Set("type", desc->typeString()); | ||
| obj.Set("sdp", desc.value()); | ||
| return obj; | ||
| } | ||
@@ -490,142 +498,147 @@ | ||
| { | ||
| PLOG_DEBUG << "addRemoteCandidate() called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| PLOG_DEBUG << "addRemoteCandidate() called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (!mRtcPeerConnPtr) | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| Napi::Error::New(env, "addRemoteCandidate() called on destroyed peer connection").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 2 || !info[0].IsString() || !info[1].IsString()) | ||
| { | ||
| Napi::TypeError::New(info.Env(), "String, String expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| try | ||
| { | ||
| std::string candidate = info[0].As<Napi::String>().ToString(); | ||
| std::string mid = info[1].As<Napi::String>().ToString(); | ||
| mRtcPeerConnPtr->addRemoteCandidate(rtc::Candidate(candidate, mid)); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error while adding remote candidate: ") + ex.what()) | ||
| .ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| } | ||
| Napi::Value PeerConnectionWrapper::createDataChannel(const Napi::CallbackInfo &info) | ||
| { | ||
| PLOG_DEBUG << "createDataChannel() called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| Napi::Error::New(env, "createDataChannel() called on destroyed peer connection").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| if (length < 1 || !info[0].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "Data Channel Label expected").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| // Optional Params | ||
| rtc::DataChannelInit init; | ||
| if (length > 1) | ||
| { | ||
| if (!info[1].IsObject()) | ||
| { | ||
| Napi::Error::New(env, "addRemoteCandidate() called on destroyed peer connection").ThrowAsJavaScriptException(); | ||
| return; | ||
| Napi::TypeError::New(env, "Data Channel Init Config expected(As Object)").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| if (length < 2 || !info[0].IsString() || !info[1].IsString()) | ||
| Napi::Object initConfig = info[1].As<Napi::Object>(); | ||
| if (!initConfig.Get("protocol").IsUndefined()) | ||
| { | ||
| Napi::TypeError::New(info.Env(), "String, String expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| if (!initConfig.Get("protocol").IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "Wrong DataChannel Init Config (protocol)").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| init.protocol = initConfig.Get("protocol").As<Napi::String>(); | ||
| } | ||
| try | ||
| if (!initConfig.Get("negotiated").IsUndefined()) | ||
| { | ||
| std::string candidate = info[0].As<Napi::String>().ToString(); | ||
| std::string mid = info[0].As<Napi::String>().ToString(); | ||
| mRtcPeerConnPtr->addRemoteCandidate(rtc::Candidate(candidate, mid)); | ||
| if (!initConfig.Get("negotiated").IsBoolean()) | ||
| { | ||
| Napi::TypeError::New(env, "Wrong DataChannel Init Config (negotiated)").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| init.negotiated = initConfig.Get("negotiated").As<Napi::Boolean>(); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error while adding remote candidate: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| } | ||
| Napi::Value PeerConnectionWrapper::createDataChannel(const Napi::CallbackInfo &info) | ||
| { | ||
| PLOG_DEBUG << "createDataChannel() called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (!mRtcPeerConnPtr) | ||
| if (!initConfig.Get("id").IsUndefined()) | ||
| { | ||
| Napi::Error::New(env, "createDataChannel() called on destroyed peer connection").ThrowAsJavaScriptException(); | ||
| if (!initConfig.Get("id").IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "Wrong DataChannel Init Config (id)").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| init.id = uint16_t(initConfig.Get("id").As<Napi::Number>().Uint32Value()); | ||
| } | ||
| if (length < 1 || !info[0].IsString()) | ||
| // Reliability.unordered parameter | ||
| if (!initConfig.Get("unordered").IsUndefined()) | ||
| { | ||
| Napi::TypeError::New(env, "Data Channel Label expected").ThrowAsJavaScriptException(); | ||
| if (!initConfig.Get("unordered").IsBoolean()) | ||
| { | ||
| Napi::TypeError::New(env, "Wrong DataChannel Init Config (unordered)").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| init.reliability.unordered = initConfig.Get("unordered").As<Napi::Boolean>(); | ||
| } | ||
| // Optional Params | ||
| rtc::DataChannelInit init; | ||
| if (length > 1) | ||
| if (!initConfig.Get("maxPacketLifeTime").IsUndefined() && !initConfig.Get("maxPacketLifeTime").IsNull() && | ||
| !initConfig.Get("maxRetransmits").IsUndefined() && !initConfig.Get("maxRetransmits").IsNull()) | ||
| { | ||
| if (!info[1].IsObject()) | ||
| { | ||
| Napi::TypeError::New(env, "Data Channel Init Config expected(As Object)").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| Napi::Object initConfig = info[1].As<Napi::Object>(); | ||
| if (!initConfig.Get("protocol").IsUndefined()) | ||
| { | ||
| if (!initConfig.Get("protocol").IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "Wrong DataChannel Init Config (protocol)").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| init.protocol = initConfig.Get("protocol").As<Napi::String>(); | ||
| } | ||
| if (!initConfig.Get("negotiated").IsUndefined()) | ||
| { | ||
| if (!initConfig.Get("negotiated").IsBoolean()) | ||
| { | ||
| Napi::TypeError::New(env, "Wrong DataChannel Init Config (negotiated)").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| init.negotiated = initConfig.Get("negotiated").As<Napi::Boolean>(); | ||
| } | ||
| if (!initConfig.Get("id").IsUndefined()) | ||
| { | ||
| if (!initConfig.Get("id").IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "Wrong DataChannel Init Config (id)").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| init.id = uint16_t(initConfig.Get("id").As<Napi::Number>().Uint32Value()); | ||
| } | ||
| // Reliability.unordered parameter | ||
| if (!initConfig.Get("unordered").IsUndefined()) | ||
| { | ||
| if (!initConfig.Get("unordered").IsBoolean()) | ||
| { | ||
| Napi::TypeError::New(env, "Wrong DataChannel Init Config (unordered)").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| init.reliability.unordered = !initConfig.Get("unordered").As<Napi::Boolean>(); | ||
| } | ||
| if (!initConfig.Get("maxPacketLifeTime").IsUndefined() && !initConfig.Get("maxPacketLifeTime").IsNull() && | ||
| !initConfig.Get("maxRetransmits").IsUndefined() && !initConfig.Get("maxRetransmits").IsNull()) | ||
| { | ||
| Napi::TypeError::New(env, "Wrong DataChannel Init Config, maxPacketLifeTime and maxRetransmits are exclusive").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| if (!initConfig.Get("maxPacketLifeTime").IsUndefined() && !initConfig.Get("maxPacketLifeTime").IsNull()) | ||
| { | ||
| if (!initConfig.Get("maxPacketLifeTime").IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "Wrong DataChannel Init Config (maxPacketLifeTime)").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| init.reliability.maxPacketLifeTime = std::chrono::milliseconds(initConfig.Get("maxPacketLifeTime").As<Napi::Number>().Uint32Value()); | ||
| } | ||
| else if (!initConfig.Get("maxRetransmits").IsUndefined() && !initConfig.Get("maxRetransmits").IsNull()) | ||
| { | ||
| if (!initConfig.Get("maxRetransmits").IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "Wrong DataChannel Init Config (maxRetransmits)").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| init.reliability.maxRetransmits = int(initConfig.Get("maxRetransmits").As<Napi::Number>().Int32Value()); | ||
| } | ||
| Napi::TypeError::New(env, "Wrong DataChannel Init Config, maxPacketLifeTime and maxRetransmits are exclusive") | ||
| .ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| try | ||
| if (!initConfig.Get("maxPacketLifeTime").IsUndefined() && !initConfig.Get("maxPacketLifeTime").IsNull()) | ||
| { | ||
| std::string label = info[0].As<Napi::String>().ToString(); | ||
| std::shared_ptr<rtc::DataChannel> dataChannel = mRtcPeerConnPtr->createDataChannel(label, std::move(init)); | ||
| auto instance = DataChannelWrapper::constructor.New({Napi::External<std::shared_ptr<rtc::DataChannel>>::New(info.Env(), &dataChannel)}); | ||
| PLOG_DEBUG << "Data Channel created. Label: " << label; | ||
| return instance; | ||
| if (!initConfig.Get("maxPacketLifeTime").IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "Wrong DataChannel Init Config (maxPacketLifeTime)").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| init.reliability.maxPacketLifeTime = | ||
| std::chrono::milliseconds(initConfig.Get("maxPacketLifeTime").As<Napi::Number>().Uint32Value()); | ||
| } | ||
| catch (std::exception &ex) | ||
| else if (!initConfig.Get("maxRetransmits").IsUndefined() && !initConfig.Get("maxRetransmits").IsNull()) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error while creating datachannel: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| if (!initConfig.Get("maxRetransmits").IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "Wrong DataChannel Init Config (maxRetransmits)").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| init.reliability.maxRetransmits = int(initConfig.Get("maxRetransmits").As<Napi::Number>().Int32Value()); | ||
| } | ||
| } | ||
| try | ||
| { | ||
| std::string label = info[0].As<Napi::String>().ToString(); | ||
| std::shared_ptr<rtc::DataChannel> dataChannel = mRtcPeerConnPtr->createDataChannel(label, std::move(init)); | ||
| auto instance = DataChannelWrapper::constructor.New( | ||
| {Napi::External<std::shared_ptr<rtc::DataChannel>>::New(info.Env(), &dataChannel)}); | ||
| PLOG_DEBUG << "Data Channel created. Label: " << label; | ||
| return instance; | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error while creating datachannel: ") + ex.what()) | ||
| .ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| } | ||
@@ -635,38 +648,40 @@ | ||
| { | ||
| PLOG_DEBUG << "onLocalDescription() called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| PLOG_DEBUG << "onLocalDescription() called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| Napi::Error::New(env, "onLocalDescription() called on destroyed peer connection").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| Napi::Error::New(env, "onLocalDescription() called on destroyed peer connection").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| // Callback | ||
| mOnLocalDescriptionCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| // Callback | ||
| mOnLocalDescriptionCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| mRtcPeerConnPtr->onLocalDescription([&](rtc::Description sdp) | ||
| { | ||
| PLOG_DEBUG << "onLocalDescription cb received from rtc"; | ||
| if (mOnLocalDescriptionCallback) | ||
| mOnLocalDescriptionCallback->call([this, sdp = std::move(sdp)](Napi::Env env, std::vector<napi_value> &args) { | ||
| PLOG_DEBUG << "mOnLocalDescriptionCallback call(1)"; | ||
| // Check the peer connection is not closed | ||
| if(instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| mRtcPeerConnPtr->onLocalDescription( | ||
| [&](rtc::Description sdp) | ||
| { | ||
| PLOG_DEBUG << "onLocalDescription cb received from rtc"; | ||
| if (mOnLocalDescriptionCallback) | ||
| mOnLocalDescriptionCallback->call( | ||
| [this, sdp = std::move(sdp)](Napi::Env env, std::vector<napi_value> &args) | ||
| { | ||
| PLOG_DEBUG << "mOnLocalDescriptionCallback call(1)"; | ||
| // Check the peer connection is not closed | ||
| if (instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| args = { | ||
| Napi::String::New(env, std::string(sdp)), | ||
| Napi::String::New(env, sdp.typeString())}; | ||
| PLOG_DEBUG << "mOnLocalDescriptionCallback call(2)"; | ||
| }); }); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| args = {Napi::String::New(env, std::string(sdp)), Napi::String::New(env, sdp.typeString())}; | ||
| PLOG_DEBUG << "mOnLocalDescriptionCallback call(2)"; | ||
| }); | ||
| }); | ||
| } | ||
@@ -676,38 +691,40 @@ | ||
| { | ||
| PLOG_DEBUG << "onLocalCandidate() called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| PLOG_DEBUG << "onLocalCandidate() called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| Napi::Error::New(env, "onLocalCandidate() called on destroyed peer connection").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| Napi::Error::New(env, "onLocalCandidate() called on destroyed peer connection").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| // Callback | ||
| mOnLocalCandidateCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| // Callback | ||
| mOnLocalCandidateCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| mRtcPeerConnPtr->onLocalCandidate([&](rtc::Candidate cand) | ||
| { | ||
| PLOG_DEBUG << "onLocalCandidate cb received from rtc"; | ||
| if (mOnLocalCandidateCallback) | ||
| mOnLocalCandidateCallback->call([this, cand = std::move(cand)](Napi::Env env, std::vector<napi_value> &args) { | ||
| PLOG_DEBUG << "mOnLocalCandidateCallback call(1)"; | ||
| // Check the peer connection is not closed | ||
| if(instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| mRtcPeerConnPtr->onLocalCandidate( | ||
| [&](rtc::Candidate cand) | ||
| { | ||
| PLOG_DEBUG << "onLocalCandidate cb received from rtc"; | ||
| if (mOnLocalCandidateCallback) | ||
| mOnLocalCandidateCallback->call( | ||
| [this, cand = std::move(cand)](Napi::Env env, std::vector<napi_value> &args) | ||
| { | ||
| PLOG_DEBUG << "mOnLocalCandidateCallback call(1)"; | ||
| // Check the peer connection is not closed | ||
| if (instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| args = { | ||
| Napi::String::New(env, std::string(cand)), | ||
| Napi::String::New(env, cand.mid())}; | ||
| PLOG_DEBUG << "mOnLocalCandidateCallback call(2)"; | ||
| }); }); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| args = {Napi::String::New(env, std::string(cand)), Napi::String::New(env, cand.mid())}; | ||
| PLOG_DEBUG << "mOnLocalCandidateCallback call(2)"; | ||
| }); | ||
| }); | ||
| } | ||
@@ -717,44 +734,50 @@ | ||
| { | ||
| PLOG_DEBUG << "onStateChange() called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| PLOG_DEBUG << "onStateChange() called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| Napi::Error::New(env, "onStateChange() called on destroyed peer connection").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| Napi::Error::New(env, "onStateChange() called on destroyed peer connection").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| // Callback | ||
| mOnStateChangeCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| // Callback | ||
| mOnStateChangeCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| mRtcPeerConnPtr->onStateChange([&](rtc::PeerConnection::State state) | ||
| { | ||
| PLOG_DEBUG << "onStateChange cb received from rtc"; | ||
| if (mOnStateChangeCallback) | ||
| mOnStateChangeCallback->call([this, state](Napi::Env env, std::vector<napi_value> &args) { | ||
| PLOG_DEBUG << "mOnStateChangeCallback call(1)"; | ||
| if(instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| mRtcPeerConnPtr->onStateChange( | ||
| [&](rtc::PeerConnection::State state) | ||
| { | ||
| PLOG_DEBUG << "onStateChange cb received from rtc"; | ||
| if (mOnStateChangeCallback) | ||
| mOnStateChangeCallback->call( | ||
| [this, state](Napi::Env env, std::vector<napi_value> &args) | ||
| { | ||
| PLOG_DEBUG << "mOnStateChangeCallback call(1)"; | ||
| if (instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| std::ostringstream stream; | ||
| stream << state; | ||
| args = {Napi::String::New(env, stream.str())}; | ||
| PLOG_DEBUG << "mOnStateChangeCallback call(2)"; | ||
| },[this,state](){ | ||
| PLOG_DEBUG << "mOnStateChangeCallback cleanup"; | ||
| // Special case for closed state, we need to reset all callbacks | ||
| if(state == rtc::PeerConnection::State::Closed) | ||
| { | ||
| doCleanup(); | ||
| } | ||
| }); }); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| std::ostringstream stream; | ||
| stream << state; | ||
| args = {Napi::String::New(env, stream.str())}; | ||
| PLOG_DEBUG << "mOnStateChangeCallback call(2)"; | ||
| }, | ||
| [this, state]() | ||
| { | ||
| PLOG_DEBUG << "mOnStateChangeCallback cleanup"; | ||
| // Special case for closed state, we need to reset all callbacks | ||
| if (state == rtc::PeerConnection::State::Closed) | ||
| { | ||
| doCleanup(); | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
@@ -764,37 +787,41 @@ | ||
| { | ||
| PLOG_DEBUG << "onIceStateChange() called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| PLOG_DEBUG << "onIceStateChange() called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| Napi::Error::New(env, "onIceStateChange() called on destroyed peer connection").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| Napi::Error::New(env, "onIceStateChange() called on destroyed peer connection").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| // Callback | ||
| mOnIceStateChangeCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| // Callback | ||
| mOnIceStateChangeCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| mRtcPeerConnPtr->onIceStateChange([&](rtc::PeerConnection::IceState state) | ||
| { | ||
| PLOG_DEBUG << "onIceStateChange cb received from rtc"; | ||
| if (mOnIceStateChangeCallback) | ||
| mOnIceStateChangeCallback->call([this, state](Napi::Env env, std::vector<napi_value> &args) { | ||
| PLOG_DEBUG << "mOnIceStateChangeCallback call(1)"; | ||
| if(instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| mRtcPeerConnPtr->onIceStateChange( | ||
| [&](rtc::PeerConnection::IceState state) | ||
| { | ||
| PLOG_DEBUG << "onIceStateChange cb received from rtc"; | ||
| if (mOnIceStateChangeCallback) | ||
| mOnIceStateChangeCallback->call( | ||
| [this, state](Napi::Env env, std::vector<napi_value> &args) | ||
| { | ||
| PLOG_DEBUG << "mOnIceStateChangeCallback call(1)"; | ||
| if (instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| std::ostringstream stream; | ||
| stream << state; | ||
| args = {Napi::String::New(env, stream.str())}; | ||
| PLOG_DEBUG << "mOnIceStateChangeCallback call(2)"; | ||
| }); }); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| std::ostringstream stream; | ||
| stream << state; | ||
| args = {Napi::String::New(env, stream.str())}; | ||
| PLOG_DEBUG << "mOnIceStateChangeCallback call(2)"; | ||
| }); | ||
| }); | ||
| } | ||
@@ -804,38 +831,42 @@ | ||
| { | ||
| PLOG_DEBUG << "onSignalingStateChange() called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| PLOG_DEBUG << "onSignalingStateChange() called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| Napi::Error::New(env, "onSignalingStateChange() called on destroyed peer connection").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| Napi::Error::New(env, "onSignalingStateChange() called on destroyed peer connection").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| // Callback | ||
| mOnSignalingStateChangeCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| // Callback | ||
| mOnSignalingStateChangeCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| mRtcPeerConnPtr->onSignalingStateChange([&](rtc::PeerConnection::SignalingState state) | ||
| { | ||
| PLOG_DEBUG << "onSignalingStateChange cb received from rtc"; | ||
| if (mOnSignalingStateChangeCallback) | ||
| mOnSignalingStateChangeCallback->call([this, state](Napi::Env env, std::vector<napi_value> &args) { | ||
| PLOG_DEBUG << "mOnSignalingStateChangeCallback call(1)"; | ||
| // Check the peer connection is not closed | ||
| if(instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| mRtcPeerConnPtr->onSignalingStateChange( | ||
| [&](rtc::PeerConnection::SignalingState state) | ||
| { | ||
| PLOG_DEBUG << "onSignalingStateChange cb received from rtc"; | ||
| if (mOnSignalingStateChangeCallback) | ||
| mOnSignalingStateChangeCallback->call( | ||
| [this, state](Napi::Env env, std::vector<napi_value> &args) | ||
| { | ||
| PLOG_DEBUG << "mOnSignalingStateChangeCallback call(1)"; | ||
| // Check the peer connection is not closed | ||
| if (instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| std::ostringstream stream; | ||
| stream << state; | ||
| args = {Napi::String::New(env, stream.str())}; | ||
| PLOG_DEBUG << "mOnSignalingStateChangeCallback call(2)"; | ||
| }); }); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| std::ostringstream stream; | ||
| stream << state; | ||
| args = {Napi::String::New(env, stream.str())}; | ||
| PLOG_DEBUG << "mOnSignalingStateChangeCallback call(2)"; | ||
| }); | ||
| }); | ||
| } | ||
@@ -845,38 +876,42 @@ | ||
| { | ||
| PLOG_DEBUG << "onGatheringStateChange() called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| PLOG_DEBUG << "onGatheringStateChange() called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| Napi::Error::New(env, "onGatheringStateChange() called on destroyed peer connection").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| Napi::Error::New(env, "onGatheringStateChange() called on destroyed peer connection").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| // Callback | ||
| mOnGatheringStateChangeCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| // Callback | ||
| mOnGatheringStateChangeCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| mRtcPeerConnPtr->onGatheringStateChange([&](rtc::PeerConnection::GatheringState state) | ||
| { | ||
| PLOG_DEBUG << "onGatheringStateChange cb received from rtc"; | ||
| if (mOnGatheringStateChangeCallback) | ||
| mOnGatheringStateChangeCallback->call([this, state](Napi::Env env, std::vector<napi_value> &args) { | ||
| PLOG_DEBUG << "mOnGatheringStateChangeCallback call(1)"; | ||
| // Check the peer connection is not closed | ||
| if(instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| mRtcPeerConnPtr->onGatheringStateChange( | ||
| [&](rtc::PeerConnection::GatheringState state) | ||
| { | ||
| PLOG_DEBUG << "onGatheringStateChange cb received from rtc"; | ||
| if (mOnGatheringStateChangeCallback) | ||
| mOnGatheringStateChangeCallback->call( | ||
| [this, state](Napi::Env env, std::vector<napi_value> &args) | ||
| { | ||
| PLOG_DEBUG << "mOnGatheringStateChangeCallback call(1)"; | ||
| // Check the peer connection is not closed | ||
| if (instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| std::ostringstream stream; | ||
| stream << state; | ||
| args = {Napi::String::New(env, stream.str())}; | ||
| PLOG_DEBUG << "mOnGatheringStateChangeCallback call(2)"; | ||
| }); }); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| std::ostringstream stream; | ||
| stream << state; | ||
| args = {Napi::String::New(env, stream.str())}; | ||
| PLOG_DEBUG << "mOnGatheringStateChangeCallback call(2)"; | ||
| }); | ||
| }); | ||
| } | ||
@@ -886,38 +921,43 @@ | ||
| { | ||
| PLOG_DEBUG << "onDataChannel() called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| PLOG_DEBUG << "onDataChannel() called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| Napi::Error::New(env, "onDataChannel() called on destroyed peer connection").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| Napi::Error::New(env, "onDataChannel() called on destroyed peer connection").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| // Callback | ||
| mOnDataChannelCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| // Callback | ||
| mOnDataChannelCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| mRtcPeerConnPtr->onDataChannel([&](std::shared_ptr<rtc::DataChannel> dc) | ||
| { | ||
| PLOG_DEBUG << "onDataChannel cb received from rtc"; | ||
| if (mOnDataChannelCallback) | ||
| mOnDataChannelCallback->call([this, dc](Napi::Env env, std::vector<napi_value> &args) { | ||
| PLOG_DEBUG << "mOnDataChannelCallback call(1)"; | ||
| // Check the peer connection is not closed | ||
| if(instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| mRtcPeerConnPtr->onDataChannel( | ||
| [&](std::shared_ptr<rtc::DataChannel> dc) | ||
| { | ||
| PLOG_DEBUG << "onDataChannel cb received from rtc"; | ||
| if (mOnDataChannelCallback) | ||
| mOnDataChannelCallback->call( | ||
| [this, dc](Napi::Env env, std::vector<napi_value> &args) | ||
| { | ||
| PLOG_DEBUG << "mOnDataChannelCallback call(1)"; | ||
| // Check the peer connection is not closed | ||
| if (instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| std::shared_ptr<rtc::DataChannel> dataChannel = dc; | ||
| auto instance = DataChannelWrapper::constructor.New({Napi::External<std::shared_ptr<rtc::DataChannel>>::New(env, &dataChannel)}); | ||
| args = {instance}; | ||
| PLOG_DEBUG << "mOnDataChannelCallback call(2)"; | ||
| }); }); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| std::shared_ptr<rtc::DataChannel> dataChannel = dc; | ||
| auto instance = DataChannelWrapper::constructor.New( | ||
| {Napi::External<std::shared_ptr<rtc::DataChannel>>::New(env, &dataChannel)}); | ||
| args = {instance}; | ||
| PLOG_DEBUG << "mOnDataChannelCallback call(2)"; | ||
| }); | ||
| }); | ||
| } | ||
@@ -927,19 +967,19 @@ | ||
| { | ||
| PLOG_DEBUG << "bytesSent() called"; | ||
| Napi::Env env = info.Env(); | ||
| PLOG_DEBUG << "bytesSent() called"; | ||
| Napi::Env env = info.Env(); | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| try | ||
| { | ||
| return Napi::Number::New(env, mRtcPeerConnPtr->bytesSent()); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| try | ||
| { | ||
| return Napi::Number::New(env, mRtcPeerConnPtr->bytesSent()); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| } | ||
@@ -949,19 +989,19 @@ | ||
| { | ||
| PLOG_DEBUG << "bytesReceived() called"; | ||
| Napi::Env env = info.Env(); | ||
| PLOG_DEBUG << "bytesReceived() called"; | ||
| Napi::Env env = info.Env(); | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| try | ||
| { | ||
| return Napi::Number::New(env, mRtcPeerConnPtr->bytesReceived()); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| try | ||
| { | ||
| return Napi::Number::New(env, mRtcPeerConnPtr->bytesReceived()); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| } | ||
@@ -971,19 +1011,19 @@ | ||
| { | ||
| PLOG_DEBUG << "rtt() called"; | ||
| Napi::Env env = info.Env(); | ||
| PLOG_DEBUG << "rtt() called"; | ||
| Napi::Env env = info.Env(); | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| try | ||
| { | ||
| return Napi::Number::New(env, mRtcPeerConnPtr->rtt().value_or(std::chrono::milliseconds(-1)).count()); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Number::New(info.Env(), -1); | ||
| } | ||
| try | ||
| { | ||
| return Napi::Number::New(env, mRtcPeerConnPtr->rtt().value_or(std::chrono::milliseconds(-1)).count()); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Number::New(info.Env(), -1); | ||
| } | ||
| } | ||
@@ -993,46 +1033,46 @@ | ||
| { | ||
| PLOG_DEBUG << "getSelectedCandidatePair() called"; | ||
| Napi::Env env = info.Env(); | ||
| PLOG_DEBUG << "getSelectedCandidatePair() called"; | ||
| Napi::Env env = info.Env(); | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| return env.Null(); | ||
| } | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| return env.Null(); | ||
| } | ||
| try | ||
| { | ||
| rtc::Candidate local, remote; | ||
| if (!mRtcPeerConnPtr->getSelectedCandidatePair(&local, &remote)) | ||
| return env.Null(); | ||
| try | ||
| { | ||
| rtc::Candidate local, remote; | ||
| if (!mRtcPeerConnPtr->getSelectedCandidatePair(&local, &remote)) | ||
| return env.Null(); | ||
| Napi::Object retvalue = Napi::Object::New(env); | ||
| Napi::Object localObj = Napi::Object::New(env); | ||
| Napi::Object remoteObj = Napi::Object::New(env); | ||
| Napi::Object retvalue = Napi::Object::New(env); | ||
| Napi::Object localObj = Napi::Object::New(env); | ||
| Napi::Object remoteObj = Napi::Object::New(env); | ||
| localObj.Set("address", local.address().value_or("?")); | ||
| localObj.Set("port", local.port().value_or(0)); | ||
| localObj.Set("type", candidateTypeToString(local.type())); | ||
| localObj.Set("transportType", candidateTransportTypeToString(local.transportType())); | ||
| localObj.Set("candidate", local.candidate()); | ||
| localObj.Set("mid", local.mid()); | ||
| localObj.Set("priority", local.priority()); | ||
| localObj.Set("address", local.address().value_or("?")); | ||
| localObj.Set("port", local.port().value_or(0)); | ||
| localObj.Set("type", candidateTypeToString(local.type())); | ||
| localObj.Set("transportType", candidateTransportTypeToString(local.transportType())); | ||
| localObj.Set("candidate", local.candidate()); | ||
| localObj.Set("mid", local.mid()); | ||
| localObj.Set("priority", local.priority()); | ||
| remoteObj.Set("address", remote.address().value_or("?")); | ||
| remoteObj.Set("port", remote.port().value_or(0)); | ||
| remoteObj.Set("type", candidateTypeToString(remote.type())); | ||
| remoteObj.Set("transportType", candidateTransportTypeToString(remote.transportType())); | ||
| remoteObj.Set("candidate", remote.candidate()); | ||
| remoteObj.Set("mid", remote.mid()); | ||
| remoteObj.Set("priority", remote.priority()); | ||
| remoteObj.Set("address", remote.address().value_or("?")); | ||
| remoteObj.Set("port", remote.port().value_or(0)); | ||
| remoteObj.Set("type", candidateTypeToString(remote.type())); | ||
| remoteObj.Set("transportType", candidateTransportTypeToString(remote.transportType())); | ||
| remoteObj.Set("candidate", remote.candidate()); | ||
| remoteObj.Set("mid", remote.mid()); | ||
| remoteObj.Set("priority", remote.priority()); | ||
| retvalue.Set("local", localObj); | ||
| retvalue.Set("remote", remoteObj); | ||
| retvalue.Set("local", localObj); | ||
| retvalue.Set("remote", remoteObj); | ||
| return retvalue; | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Number::New(info.Env(), -1); | ||
| } | ||
| return retvalue; | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Number::New(info.Env(), -1); | ||
| } | ||
| } | ||
@@ -1042,19 +1082,19 @@ | ||
| { | ||
| PLOG_DEBUG << "maxDataChannelId() called"; | ||
| Napi::Env env = info.Env(); | ||
| PLOG_DEBUG << "maxDataChannelId() called"; | ||
| Napi::Env env = info.Env(); | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| try | ||
| { | ||
| return Napi::Number::New(env, mRtcPeerConnPtr->maxDataChannelId()); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| try | ||
| { | ||
| return Napi::Number::New(env, mRtcPeerConnPtr->maxDataChannelId()); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| } | ||
@@ -1064,19 +1104,19 @@ | ||
| { | ||
| PLOG_DEBUG << "maxMessageSize() called"; | ||
| Napi::Env env = info.Env(); | ||
| PLOG_DEBUG << "maxMessageSize() called"; | ||
| Napi::Env env = info.Env(); | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| try | ||
| { | ||
| return Napi::Array::New(env, mRtcPeerConnPtr->remoteMaxMessageSize()); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| try | ||
| { | ||
| return Napi::Number::New(env, mRtcPeerConnPtr->remoteMaxMessageSize()); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| } | ||
@@ -1086,25 +1126,25 @@ | ||
| { | ||
| PLOG_DEBUG << "remoteFingerprints() called"; | ||
| Napi::Env env = info.Env(); | ||
| PLOG_DEBUG << "remoteFingerprints() called"; | ||
| Napi::Env env = info.Env(); | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| try | ||
| { | ||
| auto fingerprint = mRtcPeerConnPtr->remoteFingerprint(); | ||
| try | ||
| { | ||
| auto fingerprint = mRtcPeerConnPtr->remoteFingerprint(); | ||
| Napi::Object fingerprintObject = Napi::Object::New(env); | ||
| fingerprintObject.Set("value", fingerprint.value); | ||
| fingerprintObject.Set("algorithm", rtc::CertificateFingerprint::AlgorithmIdentifier(fingerprint.algorithm)); | ||
| Napi::Object fingerprintObject = Napi::Object::New(env); | ||
| fingerprintObject.Set("value", fingerprint.value); | ||
| fingerprintObject.Set("algorithm", rtc::CertificateFingerprint::AlgorithmIdentifier(fingerprint.algorithm)); | ||
| return fingerprintObject; | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| return fingerprintObject; | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| } | ||
@@ -1114,16 +1154,16 @@ | ||
| { | ||
| PLOG_DEBUG << "candidateTypeToString() called"; | ||
| switch (type) | ||
| { | ||
| case rtc::Candidate::Type::Host: | ||
| return "host"; | ||
| case rtc::Candidate::Type::PeerReflexive: | ||
| return "prflx"; | ||
| case rtc::Candidate::Type::ServerReflexive: | ||
| return "srflx"; | ||
| case rtc::Candidate::Type::Relayed: | ||
| return "relay"; | ||
| default: | ||
| return "unknown"; | ||
| } | ||
| PLOG_DEBUG << "candidateTypeToString() called"; | ||
| switch (type) | ||
| { | ||
| case rtc::Candidate::Type::Host: | ||
| return "host"; | ||
| case rtc::Candidate::Type::PeerReflexive: | ||
| return "prflx"; | ||
| case rtc::Candidate::Type::ServerReflexive: | ||
| return "srflx"; | ||
| case rtc::Candidate::Type::Relayed: | ||
| return "relay"; | ||
| default: | ||
| return "unknown"; | ||
| } | ||
| } | ||
@@ -1133,18 +1173,18 @@ | ||
| { | ||
| PLOG_DEBUG << "candidateTransportTypeToString() called"; | ||
| switch (transportType) | ||
| { | ||
| case rtc::Candidate::TransportType::Udp: | ||
| return "UDP"; | ||
| case rtc::Candidate::TransportType::TcpActive: | ||
| return "TCP_active"; | ||
| case rtc::Candidate::TransportType::TcpPassive: | ||
| return "TCP_passive"; | ||
| case rtc::Candidate::TransportType::TcpSo: | ||
| return "TCP_so"; | ||
| case rtc::Candidate::TransportType::TcpUnknown: | ||
| return "TCP_unknown"; | ||
| default: | ||
| return "unknown"; | ||
| } | ||
| PLOG_DEBUG << "candidateTransportTypeToString() called"; | ||
| switch (transportType) | ||
| { | ||
| case rtc::Candidate::TransportType::Udp: | ||
| return "UDP"; | ||
| case rtc::Candidate::TransportType::TcpActive: | ||
| return "TCP_active"; | ||
| case rtc::Candidate::TransportType::TcpPassive: | ||
| return "TCP_passive"; | ||
| case rtc::Candidate::TransportType::TcpSo: | ||
| return "TCP_so"; | ||
| case rtc::Candidate::TransportType::TcpUnknown: | ||
| return "TCP_unknown"; | ||
| default: | ||
| return "unknown"; | ||
| } | ||
| } | ||
@@ -1155,45 +1195,47 @@ | ||
| { | ||
| PLOG_DEBUG << "addTrack() called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| PLOG_DEBUG << "addTrack() called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (!mRtcPeerConnPtr) | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| Napi::Error::New(env, "addTrack() called on destroyed peer connection").ThrowAsJavaScriptException(); | ||
| return env.Null(); | ||
| } | ||
| if (length < 1 || !info[0].IsObject()) | ||
| { | ||
| Napi::TypeError::New(env, "Media class instance expected").ThrowAsJavaScriptException(); | ||
| return env.Null(); | ||
| } | ||
| try | ||
| { | ||
| Napi::Object obj = info[0].As<Napi::Object>(); | ||
| if (obj.Get("media-type-video").IsBoolean()) | ||
| { | ||
| Napi::Error::New(env, "addTrack() called on destroyed peer connection").ThrowAsJavaScriptException(); | ||
| return env.Null(); | ||
| VideoWrapper *videoPtr = Napi::ObjectWrap<VideoWrapper>::Unwrap(obj); | ||
| std::shared_ptr<rtc::Track> track = mRtcPeerConnPtr->addTrack(videoPtr->getVideoInstance()); | ||
| auto instance = | ||
| TrackWrapper::constructor.New({Napi::External<std::shared_ptr<rtc::Track>>::New(info.Env(), &track)}); | ||
| return instance; | ||
| } | ||
| if (length < 1 || !info[0].IsObject()) | ||
| if (obj.Get("media-type-audio").IsBoolean()) | ||
| { | ||
| Napi::TypeError::New(env, "Media class instance expected").ThrowAsJavaScriptException(); | ||
| return env.Null(); | ||
| AudioWrapper *audioPtr = Napi::ObjectWrap<AudioWrapper>::Unwrap(obj); | ||
| std::shared_ptr<rtc::Track> track = mRtcPeerConnPtr->addTrack(audioPtr->getAudioInstance()); | ||
| auto instance = | ||
| TrackWrapper::constructor.New({Napi::External<std::shared_ptr<rtc::Track>>::New(info.Env(), &track)}); | ||
| return instance; | ||
| } | ||
| try | ||
| { | ||
| Napi::Object obj = info[0].As<Napi::Object>(); | ||
| if (obj.Get("media-type-video").IsBoolean()) | ||
| { | ||
| VideoWrapper *videoPtr = Napi::ObjectWrap<VideoWrapper>::Unwrap(obj); | ||
| std::shared_ptr<rtc::Track> track = mRtcPeerConnPtr->addTrack(videoPtr->getVideoInstance()); | ||
| auto instance = TrackWrapper::constructor.New({Napi::External<std::shared_ptr<rtc::Track>>::New(info.Env(), &track)}); | ||
| return instance; | ||
| } | ||
| if (obj.Get("media-type-audio").IsBoolean()) | ||
| { | ||
| AudioWrapper *audioPtr = Napi::ObjectWrap<AudioWrapper>::Unwrap(obj); | ||
| std::shared_ptr<rtc::Track> track = mRtcPeerConnPtr->addTrack(audioPtr->getAudioInstance()); | ||
| auto instance = TrackWrapper::constructor.New({Napi::External<std::shared_ptr<rtc::Track>>::New(info.Env(), &track)}); | ||
| return instance; | ||
| } | ||
| Napi::Error::New(env, std::string("Unknown media type")).ThrowAsJavaScriptException(); | ||
| return env.Null(); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return env.Null(); | ||
| } | ||
| Napi::Error::New(env, std::string("Unknown media type")).ThrowAsJavaScriptException(); | ||
| return env.Null(); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return env.Null(); | ||
| } | ||
| } | ||
@@ -1203,35 +1245,40 @@ | ||
| { | ||
| PLOG_DEBUG << "onTrack() called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| PLOG_DEBUG << "onTrack() called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| Napi::Error::New(env, "onGatheringStateChange() called on destroyed peer connection").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (!mRtcPeerConnPtr) | ||
| { | ||
| Napi::Error::New(env, "onGatheringStateChange() called on destroyed peer connection").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| // Callback | ||
| mOnTrackCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| // Callback | ||
| mOnTrackCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| mRtcPeerConnPtr->onTrack([&](std::shared_ptr<rtc::Track> track) | ||
| { | ||
| if (mOnTrackCallback) | ||
| mOnTrackCallback->call([this, track](Napi::Env env, std::vector<napi_value> &args) { | ||
| // Check the peer connection is not closed | ||
| if(instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| mRtcPeerConnPtr->onTrack( | ||
| [&](std::shared_ptr<rtc::Track> track) | ||
| { | ||
| if (mOnTrackCallback) | ||
| mOnTrackCallback->call( | ||
| [this, track](Napi::Env env, std::vector<napi_value> &args) | ||
| { | ||
| // Check the peer connection is not closed | ||
| if (instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| std::shared_ptr<rtc::Track> newTrack = track; | ||
| auto instance = TrackWrapper::constructor.New({Napi::External<std::shared_ptr<rtc::Track>>::New(env, &newTrack)}); | ||
| args = {instance}; | ||
| }); }); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| std::shared_ptr<rtc::Track> newTrack = track; | ||
| auto instance = | ||
| TrackWrapper::constructor.New({Napi::External<std::shared_ptr<rtc::Track>>::New(env, &newTrack)}); | ||
| args = {instance}; | ||
| }); | ||
| }); | ||
| } | ||
@@ -1242,5 +1289,5 @@ #endif | ||
| { | ||
| PLOG_DEBUG << "hasMedia() called"; | ||
| Napi::Env env = info.Env(); | ||
| return Napi::Boolean::New(env, mRtcPeerConnPtr ? mRtcPeerConnPtr->hasMedia() : false); | ||
| PLOG_DEBUG << "hasMedia() called"; | ||
| Napi::Env env = info.Env(); | ||
| return Napi::Boolean::New(env, mRtcPeerConnPtr ? mRtcPeerConnPtr->hasMedia() : false); | ||
| } | ||
@@ -1250,7 +1297,7 @@ | ||
| { | ||
| PLOG_DEBUG << "state() called"; | ||
| Napi::Env env = info.Env(); | ||
| std::ostringstream stream; | ||
| stream << (mRtcPeerConnPtr ? mRtcPeerConnPtr->state() : rtc::PeerConnection::State::Closed); | ||
| return Napi::String::New(env, stream.str()); | ||
| PLOG_DEBUG << "state() called"; | ||
| Napi::Env env = info.Env(); | ||
| std::ostringstream stream; | ||
| stream << (mRtcPeerConnPtr ? mRtcPeerConnPtr->state() : rtc::PeerConnection::State::Closed); | ||
| return Napi::String::New(env, stream.str()); | ||
| } | ||
@@ -1260,7 +1307,7 @@ | ||
| { | ||
| PLOG_DEBUG << "iceState() called"; | ||
| Napi::Env env = info.Env(); | ||
| std::ostringstream stream; | ||
| stream << (mRtcPeerConnPtr ? mRtcPeerConnPtr->iceState() : rtc::PeerConnection::IceState::Closed); | ||
| return Napi::String::New(env, stream.str()); | ||
| PLOG_DEBUG << "iceState() called"; | ||
| Napi::Env env = info.Env(); | ||
| std::ostringstream stream; | ||
| stream << (mRtcPeerConnPtr ? mRtcPeerConnPtr->iceState() : rtc::PeerConnection::IceState::Closed); | ||
| return Napi::String::New(env, stream.str()); | ||
| } | ||
@@ -1270,7 +1317,7 @@ | ||
| { | ||
| PLOG_DEBUG << "signalingState() called"; | ||
| Napi::Env env = info.Env(); | ||
| std::ostringstream stream; | ||
| stream << (mRtcPeerConnPtr ? mRtcPeerConnPtr->signalingState() : rtc::PeerConnection::SignalingState::Stable); | ||
| return Napi::String::New(env, stream.str()); | ||
| PLOG_DEBUG << "signalingState() called"; | ||
| Napi::Env env = info.Env(); | ||
| std::ostringstream stream; | ||
| stream << (mRtcPeerConnPtr ? mRtcPeerConnPtr->signalingState() : rtc::PeerConnection::SignalingState::Stable); | ||
| return Napi::String::New(env, stream.str()); | ||
| } | ||
@@ -1280,7 +1327,7 @@ | ||
| { | ||
| PLOG_DEBUG << "gatheringState() called"; | ||
| Napi::Env env = info.Env(); | ||
| std::ostringstream stream; | ||
| stream << (mRtcPeerConnPtr ? mRtcPeerConnPtr->gatheringState() : rtc::PeerConnection::GatheringState::Complete); | ||
| return Napi::String::New(env, stream.str()); | ||
| PLOG_DEBUG << "gatheringState() called"; | ||
| Napi::Env env = info.Env(); | ||
| std::ostringstream stream; | ||
| stream << (mRtcPeerConnPtr ? mRtcPeerConnPtr->gatheringState() : rtc::PeerConnection::GatheringState::Complete); | ||
| return Napi::String::New(env, stream.str()); | ||
| } |
| #ifndef PEER_CONNECTION_WRAPPER_H | ||
| #define PEER_CONNECTION_WRAPPER_H | ||
| #include <iostream> | ||
| #include <string> | ||
| #include <variant> | ||
| #include <memory> | ||
@@ -8,0 +6,0 @@ #include <unordered_set> |
+134
-130
@@ -21,11 +21,11 @@ #include "rtc-wrapper.h" | ||
| { | ||
| Napi::HandleScope scope(env); | ||
| Napi::HandleScope scope(env); | ||
| exports.Set("initLogger", Napi::Function::New(env, &RtcWrapper::initLogger)); | ||
| exports.Set("cleanup", Napi::Function::New(env, &RtcWrapper::cleanup)); | ||
| exports.Set("preload", Napi::Function::New(env, &RtcWrapper::preload)); | ||
| exports.Set("setSctpSettings", Napi::Function::New(env, &RtcWrapper::setSctpSettings)); | ||
| exports.Set("getLibraryVersion", Napi::Function::New(env, &RtcWrapper::getLibraryVersion)); | ||
| exports.Set("initLogger", Napi::Function::New(env, &RtcWrapper::initLogger)); | ||
| exports.Set("cleanup", Napi::Function::New(env, &RtcWrapper::cleanup)); | ||
| exports.Set("preload", Napi::Function::New(env, &RtcWrapper::preload)); | ||
| exports.Set("setSctpSettings", Napi::Function::New(env, &RtcWrapper::setSctpSettings)); | ||
| exports.Set("getLibraryVersion", Napi::Function::New(env, &RtcWrapper::getLibraryVersion)); | ||
| return exports; | ||
| return exports; | ||
| } | ||
@@ -35,12 +35,12 @@ | ||
| { | ||
| PLOG_DEBUG << "preload() called"; | ||
| Napi::Env env = info.Env(); | ||
| try | ||
| { | ||
| rtc::Preload(); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error# ") + ex.what()).ThrowAsJavaScriptException(); | ||
| } | ||
| PLOG_DEBUG << "preload() called"; | ||
| Napi::Env env = info.Env(); | ||
| try | ||
| { | ||
| rtc::Preload(); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error# ") + ex.what()).ThrowAsJavaScriptException(); | ||
| } | ||
| } | ||
@@ -50,71 +50,75 @@ | ||
| { | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| // We expect (String, Object, Function) as param | ||
| if (length < 1 || !info[0].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "LogLevel(String) expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| // We expect (String, Object, Function) as param | ||
| if (length < 1 || !info[0].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "LogLevel(String) expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| std::string logLevelStr = info[0].As<Napi::String>().ToString(); | ||
| rtc::LogLevel logLevel = rtc::LogLevel::None; | ||
| std::string logLevelStr = info[0].As<Napi::String>().ToString(); | ||
| rtc::LogLevel logLevel = rtc::LogLevel::None; | ||
| if (logLevelStr == "Verbose") | ||
| logLevel = rtc::LogLevel::Verbose; | ||
| if (logLevelStr == "Debug") | ||
| logLevel = rtc::LogLevel::Debug; | ||
| if (logLevelStr == "Info") | ||
| logLevel = rtc::LogLevel::Info; | ||
| if (logLevelStr == "Warning") | ||
| logLevel = rtc::LogLevel::Warning; | ||
| if (logLevelStr == "Error") | ||
| logLevel = rtc::LogLevel::Error; | ||
| if (logLevelStr == "Fatal") | ||
| logLevel = rtc::LogLevel::Fatal; | ||
| if (logLevelStr == "Verbose") | ||
| logLevel = rtc::LogLevel::Verbose; | ||
| if (logLevelStr == "Debug") | ||
| logLevel = rtc::LogLevel::Debug; | ||
| if (logLevelStr == "Info") | ||
| logLevel = rtc::LogLevel::Info; | ||
| if (logLevelStr == "Warning") | ||
| logLevel = rtc::LogLevel::Warning; | ||
| if (logLevelStr == "Error") | ||
| logLevel = rtc::LogLevel::Error; | ||
| if (logLevelStr == "Fatal") | ||
| logLevel = rtc::LogLevel::Fatal; | ||
| try | ||
| try | ||
| { | ||
| if (length < 2) | ||
| { | ||
| if (length < 2) | ||
| { | ||
| rtc::InitLogger(logLevel); | ||
| } | ||
| else | ||
| { | ||
| if (!info[1].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| logCallback = std::make_unique<ThreadSafeCallback>(info[1].As<Napi::Function>()); | ||
| rtc::InitLogger(logLevel, [&](rtc::LogLevel level, std::string message) | ||
| { | ||
| if (logCallback) | ||
| logCallback->call([level, message = std::move(message)](Napi::Env env, std::vector<napi_value> &args) { | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| std::string logLevel; | ||
| if (level == rtc::LogLevel::Verbose) | ||
| logLevel = "Verbose"; | ||
| if (level == rtc::LogLevel::Debug) | ||
| logLevel = "Debug"; | ||
| if (level == rtc::LogLevel::Info) | ||
| logLevel = "Info"; | ||
| if (level == rtc::LogLevel::Warning) | ||
| logLevel = "Warning"; | ||
| if (level == rtc::LogLevel::Error) | ||
| logLevel = "Error"; | ||
| if (level == rtc::LogLevel::Fatal) | ||
| logLevel = "Fatal"; | ||
| args = {Napi::String::New(env, logLevel), Napi::String::New(env, message)}; | ||
| }); }); | ||
| } | ||
| rtc::InitLogger(logLevel); | ||
| } | ||
| catch (std::exception &ex) | ||
| else | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error# ") + ex.what()).ThrowAsJavaScriptException(); | ||
| if (!info[1].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| logCallback = std::make_unique<ThreadSafeCallback>(info[1].As<Napi::Function>()); | ||
| rtc::InitLogger(logLevel, | ||
| [&](rtc::LogLevel level, std::string message) | ||
| { | ||
| if (logCallback) | ||
| logCallback->call( | ||
| [level, message = std::move(message)](Napi::Env env, std::vector<napi_value> &args) | ||
| { | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| std::string logLevel; | ||
| if (level == rtc::LogLevel::Verbose) | ||
| logLevel = "Verbose"; | ||
| if (level == rtc::LogLevel::Debug) | ||
| logLevel = "Debug"; | ||
| if (level == rtc::LogLevel::Info) | ||
| logLevel = "Info"; | ||
| if (level == rtc::LogLevel::Warning) | ||
| logLevel = "Warning"; | ||
| if (level == rtc::LogLevel::Error) | ||
| logLevel = "Error"; | ||
| if (level == rtc::LogLevel::Fatal) | ||
| logLevel = "Fatal"; | ||
| args = {Napi::String::New(env, logLevel), Napi::String::New(env, message)}; | ||
| }); | ||
| }); | ||
| } | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error# ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| } | ||
@@ -124,42 +128,42 @@ | ||
| { | ||
| PLOG_DEBUG << "cleanup() called"; | ||
| Napi::Env env = info.Env(); | ||
| try | ||
| { | ||
| PeerConnectionWrapper::CloseAll(); | ||
| DataChannelWrapper::CloseAll(); | ||
| PLOG_DEBUG << "cleanup() called"; | ||
| Napi::Env env = info.Env(); | ||
| try | ||
| { | ||
| PeerConnectionWrapper::CloseAll(); | ||
| DataChannelWrapper::CloseAll(); | ||
| #if RTC_ENABLE_MEDIA == 1 | ||
| TrackWrapper::CloseAll(); | ||
| TrackWrapper::CloseAll(); | ||
| #endif | ||
| #if RTC_ENABLE_WEBSOCKET == 1 | ||
| WebSocketWrapper::CloseAll(); | ||
| WebSocketServerWrapper::StopAll(); | ||
| WebSocketWrapper::CloseAll(); | ||
| WebSocketServerWrapper::StopAll(); | ||
| #endif | ||
| const auto timeout = std::chrono::seconds(10); | ||
| if (rtc::Cleanup().wait_for(std::chrono::seconds(timeout)) == std::future_status::timeout) | ||
| throw std::runtime_error("cleanup timeout (possible deadlock)"); | ||
| const auto timeout = std::chrono::seconds(10); | ||
| if (rtc::Cleanup().wait_for(std::chrono::seconds(timeout)) == std::future_status::timeout) | ||
| throw std::runtime_error("cleanup timeout (possible deadlock)"); | ||
| // Cleanup the instances | ||
| PeerConnectionWrapper::CleanupAll(); | ||
| DataChannelWrapper::CleanupAll(); | ||
| // Cleanup the instances | ||
| PeerConnectionWrapper::CleanupAll(); | ||
| DataChannelWrapper::CleanupAll(); | ||
| #if RTC_ENABLE_MEDIA == 1 | ||
| TrackWrapper::CleanupAll(); | ||
| TrackWrapper::CleanupAll(); | ||
| #endif | ||
| #if RTC_ENABLE_WEBSOCKET == 1 | ||
| WebSocketWrapper::CleanupAll(); | ||
| WebSocketWrapper::CleanupAll(); | ||
| #endif | ||
| if (logCallback) | ||
| logCallback.reset(); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error# ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (logCallback) | ||
| logCallback.reset(); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error# ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| } | ||
@@ -169,38 +173,38 @@ | ||
| { | ||
| PLOG_DEBUG << "setSctpSettings() called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| PLOG_DEBUG << "setSctpSettings() called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| // We expect (Object) as param | ||
| if (length < 1 || !info[0].IsObject()) | ||
| { | ||
| Napi::TypeError::New(env, "Configuration (Object) expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| // We expect (Object) as param | ||
| if (length < 1 || !info[0].IsObject()) | ||
| { | ||
| Napi::TypeError::New(env, "Configuration (Object) expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| rtc::SctpSettings settings; | ||
| Napi::Object config = info[0].As<Napi::Object>(); | ||
| rtc::SctpSettings settings; | ||
| Napi::Object config = info[0].As<Napi::Object>(); | ||
| if (config.Get("recvBufferSize").IsNumber()) | ||
| settings.recvBufferSize = config.Get("recvBufferSize").As<Napi::Number>().Uint32Value(); | ||
| if (config.Get("sendBufferSize").IsNumber()) | ||
| settings.sendBufferSize = config.Get("sendBufferSize").As<Napi::Number>().Uint32Value(); | ||
| if (config.Get("maxChunksOnQueue").IsNumber()) | ||
| settings.maxChunksOnQueue = config.Get("maxChunksOnQueue").As<Napi::Number>().Uint32Value(); | ||
| if (config.Get("initialCongestionWindow").IsNumber()) | ||
| settings.initialCongestionWindow = config.Get("initialCongestionWindow").As<Napi::Number>().Uint32Value(); | ||
| if (config.Get("congestionControlModule").IsNumber()) | ||
| settings.congestionControlModule = config.Get("congestionControlModule").As<Napi::Number>().Uint32Value(); | ||
| if (config.Get("delayedSackTime").IsNumber()) | ||
| settings.delayedSackTime = std::chrono::milliseconds(config.Get("delayedSackTime").As<Napi::Number>().Uint32Value()); | ||
| if (config.Get("recvBufferSize").IsNumber()) | ||
| settings.recvBufferSize = config.Get("recvBufferSize").As<Napi::Number>().Uint32Value(); | ||
| if (config.Get("sendBufferSize").IsNumber()) | ||
| settings.sendBufferSize = config.Get("sendBufferSize").As<Napi::Number>().Uint32Value(); | ||
| if (config.Get("maxChunksOnQueue").IsNumber()) | ||
| settings.maxChunksOnQueue = config.Get("maxChunksOnQueue").As<Napi::Number>().Uint32Value(); | ||
| if (config.Get("initialCongestionWindow").IsNumber()) | ||
| settings.initialCongestionWindow = config.Get("initialCongestionWindow").As<Napi::Number>().Uint32Value(); | ||
| if (config.Get("congestionControlModule").IsNumber()) | ||
| settings.congestionControlModule = config.Get("congestionControlModule").As<Napi::Number>().Uint32Value(); | ||
| if (config.Get("delayedSackTime").IsNumber()) | ||
| settings.delayedSackTime = | ||
| std::chrono::milliseconds(config.Get("delayedSackTime").As<Napi::Number>().Uint32Value()); | ||
| rtc::SetSctpSettings(settings); | ||
| rtc::SetSctpSettings(settings); | ||
| } | ||
| Napi::Value RtcWrapper::getLibraryVersion(const Napi::CallbackInfo &info) | ||
| { | ||
| PLOG_DEBUG << "getLibraryVersion() called"; | ||
| Napi::Env env = info.Env(); | ||
| return Napi::String::New(info.Env(), RTC_VERSION); | ||
| PLOG_DEBUG << "getLibraryVersion() called"; | ||
| Napi::Env env = info.Env(); | ||
| return Napi::String::New(info.Env(), RTC_VERSION); | ||
| } |
| #ifndef RTC_WRAPPER_H | ||
| #define RTC_WRAPPER_H | ||
| #include <iostream> | ||
| #include <string> | ||
| #include <napi.h> | ||
@@ -16,12 +13,13 @@ | ||
| public: | ||
| static Napi::Object Init(Napi::Env env, Napi::Object exports); | ||
| static void preload(const Napi::CallbackInfo &info); | ||
| static void initLogger(const Napi::CallbackInfo &info); | ||
| static void cleanup(const Napi::CallbackInfo &info); | ||
| static void setSctpSettings(const Napi::CallbackInfo &info); | ||
| static Napi::Value getLibraryVersion(const Napi::CallbackInfo &info); | ||
| static Napi::Object Init(Napi::Env env, Napi::Object exports); | ||
| static void preload(const Napi::CallbackInfo &info); | ||
| static void initLogger(const Napi::CallbackInfo &info); | ||
| static void cleanup(const Napi::CallbackInfo &info); | ||
| static void setSctpSettings(const Napi::CallbackInfo &info); | ||
| static Napi::Value getLibraryVersion(const Napi::CallbackInfo &info); | ||
| private: | ||
| static inline std::unique_ptr<ThreadSafeCallback> logCallback = nullptr; | ||
| static inline std::unique_ptr<ThreadSafeCallback> logCallback = nullptr; | ||
| }; | ||
| #endif // RTC_WRAPPER_H |
@@ -5,65 +5,54 @@ #include "thread-safe-callback.h" | ||
| const char *ThreadSafeCallback::CancelException::what() const throw() | ||
| { | ||
| return "ThreadSafeCallback cancelled"; | ||
| } | ||
| const char *ThreadSafeCallback::CancelException::what() const throw() { return "ThreadSafeCallback cancelled"; } | ||
| ThreadSafeCallback::ThreadSafeCallback(Napi::Function callback) | ||
| { | ||
| Napi::Env env = callback.Env(); | ||
| Napi::Env env = callback.Env(); | ||
| if (!callback.IsFunction()) | ||
| throw Napi::Error::New(env, "Callback must be a function"); | ||
| if (!callback.IsFunction()) | ||
| throw Napi::Error::New(env, "Callback must be a function"); | ||
| tsfn = tsfn_t::New(env, | ||
| std::move(callback), | ||
| "ThreadSafeCallback callback", | ||
| 0, // unlimited queue | ||
| 1); | ||
| tsfn = tsfn_t::New(env, std::move(callback), "ThreadSafeCallback callback", | ||
| 0, // unlimited queue | ||
| 1); | ||
| } | ||
| ThreadSafeCallback::~ThreadSafeCallback() | ||
| { | ||
| tsfn.Abort(); | ||
| } | ||
| ThreadSafeCallback::~ThreadSafeCallback() { tsfn.Abort(); } | ||
| void ThreadSafeCallback::call(arg_func_t argFunc, cleanup_func_t cleanupFunc) | ||
| { | ||
| CallbackData *data = new CallbackData{std::move(argFunc), std::move(cleanupFunc)}; | ||
| if (tsfn.BlockingCall(data) != napi_ok) | ||
| { | ||
| delete data; | ||
| throw std::runtime_error("Failed to call JavaScript callback"); | ||
| } | ||
| CallbackData *data = new CallbackData{std::move(argFunc), std::move(cleanupFunc)}; | ||
| if (tsfn.BlockingCall(data) != napi_ok) | ||
| { | ||
| delete data; | ||
| throw std::runtime_error("Failed to call JavaScript callback"); | ||
| } | ||
| } | ||
| void ThreadSafeCallback::callbackFunc(Napi::Env env, | ||
| Napi::Function callback, | ||
| ContextType *context, | ||
| CallbackData *data) | ||
| void ThreadSafeCallback::callbackFunc(Napi::Env env, Napi::Function callback, ContextType *context, CallbackData *data) | ||
| { | ||
| // if env is gone, it could mean this cb was destroyed. See issue#176 | ||
| if (!data || !env) | ||
| return; | ||
| // if env is gone, it could mean this cb was destroyed. See issue#176 | ||
| if (!data || !env) | ||
| return; | ||
| arg_vector_t args; | ||
| arg_func_t argFunc(std::move(data->argFunc)); | ||
| cleanup_func_t cleanup(std::move(data->cleanupFunc)); | ||
| delete data; | ||
| arg_vector_t args; | ||
| arg_func_t argFunc(std::move(data->argFunc)); | ||
| cleanup_func_t cleanup(std::move(data->cleanupFunc)); | ||
| delete data; | ||
| try | ||
| { | ||
| argFunc(env, args); | ||
| } | ||
| catch (CancelException &) | ||
| { | ||
| return; | ||
| } | ||
| try | ||
| { | ||
| argFunc(env, args); | ||
| } | ||
| catch (CancelException &) | ||
| { | ||
| return; | ||
| } | ||
| if (callback) | ||
| { | ||
| callback.Call(args); | ||
| } | ||
| if (callback) | ||
| { | ||
| callback.Call(args); | ||
| } | ||
| cleanup(); | ||
| cleanup(); | ||
| } |
@@ -12,39 +12,37 @@ #ifndef THREAD_SAFE_CALLBACK_H | ||
| public: | ||
| using arg_vector_t = std::vector<napi_value>; | ||
| using arg_func_t = std::function<void(napi_env, arg_vector_t &)>; | ||
| using cleanup_func_t = std::function<void()>; | ||
| using arg_vector_t = std::vector<napi_value>; | ||
| using arg_func_t = std::function<void(napi_env, arg_vector_t &)>; | ||
| using cleanup_func_t = std::function<void()>; | ||
| ThreadSafeCallback(Napi::Function callback); | ||
| ~ThreadSafeCallback(); | ||
| ThreadSafeCallback(Napi::Function callback); | ||
| ~ThreadSafeCallback(); | ||
| ThreadSafeCallback(const ThreadSafeCallback &) = delete; | ||
| ThreadSafeCallback(ThreadSafeCallback &&) = delete; | ||
| ThreadSafeCallback(const ThreadSafeCallback &) = delete; | ||
| ThreadSafeCallback(ThreadSafeCallback &&) = delete; | ||
| ThreadSafeCallback &operator=(const ThreadSafeCallback &) = delete; | ||
| ThreadSafeCallback &operator=(ThreadSafeCallback &&) = delete; | ||
| ThreadSafeCallback &operator=(const ThreadSafeCallback &) = delete; | ||
| ThreadSafeCallback &operator=(ThreadSafeCallback &&) = delete; | ||
| void call(arg_func_t argFunc, cleanup_func_t cleanupFunc = []() {}); | ||
| void call( | ||
| arg_func_t argFunc, cleanup_func_t cleanupFunc = []() {}); | ||
| class CancelException : public std::exception | ||
| { | ||
| const char *what() const throw(); | ||
| }; | ||
| class CancelException : public std::exception | ||
| { | ||
| const char *what() const throw(); | ||
| }; | ||
| private: | ||
| using ContextType = std::nullptr_t; | ||
| struct CallbackData | ||
| { | ||
| arg_func_t argFunc; | ||
| cleanup_func_t cleanupFunc; | ||
| }; | ||
| using ContextType = std::nullptr_t; | ||
| struct CallbackData | ||
| { | ||
| arg_func_t argFunc; | ||
| cleanup_func_t cleanupFunc; | ||
| }; | ||
| static void callbackFunc(Napi::Env env, | ||
| Napi::Function callback, | ||
| ContextType *context, | ||
| CallbackData *data); | ||
| static void callbackFunc(Napi::Env env, Napi::Function callback, ContextType *context, CallbackData *data); | ||
| using tsfn_t = Napi::TypedThreadSafeFunction<ContextType, CallbackData, callbackFunc>; | ||
| tsfn_t tsfn; | ||
| using tsfn_t = Napi::TypedThreadSafeFunction<ContextType, CallbackData, callbackFunc>; | ||
| tsfn_t tsfn; | ||
| }; | ||
| #endif // THREAD_SAFE_CALLBACK_H |
| #include "web-socket-server-wrapper.h" | ||
| #include "web-socket-wrapper.h" | ||
@@ -10,6 +11,6 @@ #include "plog/Log.h" | ||
| { | ||
| PLOG_DEBUG << "StopAll() called"; | ||
| auto copy(instances); | ||
| for (auto inst : copy) | ||
| inst->doStop(); | ||
| PLOG_DEBUG << "StopAll() called"; | ||
| auto copy(instances); | ||
| for (auto inst : copy) | ||
| inst->doStop(); | ||
| } | ||
@@ -19,156 +20,157 @@ | ||
| { | ||
| Napi::HandleScope scope(env); | ||
| Napi::HandleScope scope(env); | ||
| Napi::Function func = DefineClass( | ||
| env, | ||
| "WebSocketServer", | ||
| { | ||
| InstanceMethod("stop", &WebSocketServerWrapper::stop), | ||
| InstanceMethod("port", &WebSocketServerWrapper::port), | ||
| InstanceMethod("onClient", &WebSocketServerWrapper::onClient) | ||
| }); | ||
| Napi::Function func = DefineClass(env, "WebSocketServer", | ||
| {InstanceMethod("stop", &WebSocketServerWrapper::stop), | ||
| InstanceMethod("port", &WebSocketServerWrapper::port), | ||
| InstanceMethod("onClient", &WebSocketServerWrapper::onClient)}); | ||
| // If this is not the first call, we don't want to reassign the constructor (hot-reload problem) | ||
| if(constructor.IsEmpty()) | ||
| { | ||
| constructor = Napi::Persistent(func); | ||
| constructor.SuppressDestruct(); | ||
| } | ||
| // If this is not the first call, we don't want to reassign the constructor (hot-reload problem) | ||
| if (constructor.IsEmpty()) | ||
| { | ||
| constructor = Napi::Persistent(func); | ||
| constructor.SuppressDestruct(); | ||
| } | ||
| exports.Set("WebSocketServer", func); | ||
| return exports; | ||
| exports.Set("WebSocketServer", func); | ||
| return exports; | ||
| } | ||
| WebSocketServerWrapper::WebSocketServerWrapper(const Napi::CallbackInfo &info) : Napi::ObjectWrap<WebSocketServerWrapper>(info) | ||
| WebSocketServerWrapper::WebSocketServerWrapper(const Napi::CallbackInfo &info) | ||
| : Napi::ObjectWrap<WebSocketServerWrapper>(info) | ||
| { | ||
| PLOG_DEBUG << "Constructor called"; | ||
| Napi::Env env = info.Env(); | ||
| PLOG_DEBUG << "Constructor called"; | ||
| Napi::Env env = info.Env(); | ||
| // Create WebSocketServer without config | ||
| if (info.Length() == 0) | ||
| // Create WebSocketServer without config | ||
| if (info.Length() == 0) | ||
| { | ||
| try | ||
| { | ||
| try | ||
| { | ||
| PLOG_DEBUG << "Creating a new WebSocketServer without config"; | ||
| mWebSocketServerPtr = std::make_unique<rtc::WebSocketServer>(); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error while creating WebSocketServer without config: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| PLOG_DEBUG << "Creating a new WebSocketServer without config"; | ||
| mWebSocketServerPtr = std::make_unique<rtc::WebSocketServer>(); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, | ||
| std::string("libdatachannel error while creating WebSocketServer without config: ") + ex.what()) | ||
| .ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| PLOG_DEBUG << "WebSocketServer created without config"; | ||
| PLOG_DEBUG << "WebSocketServer created without config"; | ||
| instances.insert(this); | ||
| return; | ||
| } | ||
| instances.insert(this); | ||
| return; | ||
| } | ||
| // Create WebSocketServer with config | ||
| // Create WebSocketServer with config | ||
| Napi::Object config = info[0].As<Napi::Object>(); | ||
| rtc::WebSocketServerConfiguration webSocketServerConfig; | ||
| Napi::Object config = info[0].As<Napi::Object>(); | ||
| rtc::WebSocketServerConfiguration webSocketServerConfig; | ||
| // Port | ||
| if (config.Has("port")) | ||
| // Port | ||
| if (config.Has("port")) | ||
| { | ||
| if (!config.Get("port").IsNumber()) | ||
| { | ||
| if (!config.Get("port").IsNumber()) | ||
| { | ||
| Napi::TypeError::New(info.Env(), "port must be a number").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| webSocketServerConfig.port = config.Get("port").ToNumber().Uint32Value(); | ||
| Napi::TypeError::New(info.Env(), "port must be a number").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| webSocketServerConfig.port = config.Get("port").ToNumber().Uint32Value(); | ||
| } | ||
| // Enable TLS | ||
| if (config.Has("enableTls")) | ||
| // Enable TLS | ||
| if (config.Has("enableTls")) | ||
| { | ||
| if (!config.Get("enableTls").IsBoolean()) | ||
| { | ||
| if (!config.Get("enableTls").IsBoolean()) | ||
| { | ||
| Napi::TypeError::New(info.Env(), "enableTls must be boolean").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| webSocketServerConfig.enableTls = config.Get("enableTls").ToBoolean(); | ||
| Napi::TypeError::New(info.Env(), "enableTls must be boolean").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| webSocketServerConfig.enableTls = config.Get("enableTls").ToBoolean(); | ||
| } | ||
| // Certificate PEM File | ||
| if (config.Has("certificatePemFile")) | ||
| // Certificate PEM File | ||
| if (config.Has("certificatePemFile")) | ||
| { | ||
| if (!config.Get("certificatePemFile").IsString()) | ||
| { | ||
| if (!config.Get("certificatePemFile").IsString()) | ||
| { | ||
| Napi::TypeError::New(info.Env(), "certificatePemFile must be a string").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| webSocketServerConfig.certificatePemFile = config.Get("certificatePemFile").ToString(); | ||
| Napi::TypeError::New(info.Env(), "certificatePemFile must be a string").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| webSocketServerConfig.certificatePemFile = config.Get("certificatePemFile").ToString(); | ||
| } | ||
| // Key PEM File | ||
| if (config.Has("keyPemFile")) | ||
| // Key PEM File | ||
| if (config.Has("keyPemFile")) | ||
| { | ||
| if (!config.Get("keyPemFile").IsString()) | ||
| { | ||
| if (!config.Get("keyPemFile").IsString()) | ||
| { | ||
| Napi::TypeError::New(info.Env(), "keyPemFile must be a string").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| webSocketServerConfig.keyPemFile = config.Get("keyPemFile").ToString(); | ||
| Napi::TypeError::New(info.Env(), "keyPemFile must be a string").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| webSocketServerConfig.keyPemFile = config.Get("keyPemFile").ToString(); | ||
| } | ||
| // Key PEM Pass | ||
| if (config.Has("keyPemPass")) | ||
| // Key PEM Pass | ||
| if (config.Has("keyPemPass")) | ||
| { | ||
| if (!config.Get("keyPemPass").IsString()) | ||
| { | ||
| if (!config.Get("keyPemPass").IsString()) | ||
| { | ||
| Napi::TypeError::New(info.Env(), "keyPemPass must be a string").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| webSocketServerConfig.keyPemPass = config.Get("keyPemPass").ToString(); | ||
| Napi::TypeError::New(info.Env(), "keyPemPass must be a string").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| webSocketServerConfig.keyPemPass = config.Get("keyPemPass").ToString(); | ||
| } | ||
| // Bind Address | ||
| if (config.Has("bindAddress")) | ||
| // Bind Address | ||
| if (config.Has("bindAddress")) | ||
| { | ||
| if (!config.Get("bindAddress").IsString()) | ||
| { | ||
| if (!config.Get("bindAddress").IsString()) | ||
| { | ||
| Napi::TypeError::New(info.Env(), "bindAddress must be a string").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| webSocketServerConfig.bindAddress = config.Get("bindAddress").ToString(); | ||
| Napi::TypeError::New(info.Env(), "bindAddress must be a string").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| webSocketServerConfig.bindAddress = config.Get("bindAddress").ToString(); | ||
| } | ||
| // Connection Timeout | ||
| if (config.Has("connectionTimeout")) | ||
| // Connection Timeout | ||
| if (config.Has("connectionTimeout")) | ||
| { | ||
| if (!config.Get("connectionTimeout").IsNumber()) | ||
| { | ||
| if (!config.Get("connectionTimeout").IsNumber()) | ||
| { | ||
| Napi::TypeError::New(info.Env(), "connectionTimeout must be a number").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| webSocketServerConfig.connectionTimeout = std::chrono::milliseconds(config.Get("connectionTimeout").ToNumber().Int64Value()); | ||
| Napi::TypeError::New(info.Env(), "connectionTimeout must be a number").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| webSocketServerConfig.connectionTimeout = | ||
| std::chrono::milliseconds(config.Get("connectionTimeout").ToNumber().Int64Value()); | ||
| } | ||
| // Max Message Size | ||
| if (config.Has("maxMessageSize")) | ||
| // Max Message Size | ||
| if (config.Has("maxMessageSize")) | ||
| { | ||
| if (!config.Get("maxMessageSize").IsNumber()) | ||
| { | ||
| if (!config.Get("maxMessageSize").IsNumber()) | ||
| { | ||
| Napi::TypeError::New(info.Env(), "maxMessageSize must be a number").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| webSocketServerConfig.maxMessageSize = config.Get("maxMessageSize").ToNumber().Int32Value(); | ||
| Napi::TypeError::New(info.Env(), "maxMessageSize must be a number").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| webSocketServerConfig.maxMessageSize = config.Get("maxMessageSize").ToNumber().Int32Value(); | ||
| } | ||
| // Create WebSocketServer with config | ||
| try | ||
| { | ||
| PLOG_DEBUG << "Creating a new WebSocketServer"; | ||
| mWebSocketServerPtr = std::make_unique<rtc::WebSocketServer>(webSocketServerConfig); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error while creating WebSocketServer: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| // Create WebSocketServer with config | ||
| try | ||
| { | ||
| PLOG_DEBUG << "Creating a new WebSocketServer"; | ||
| mWebSocketServerPtr = std::make_unique<rtc::WebSocketServer>(webSocketServerConfig); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error while creating WebSocketServer: ") + ex.what()) | ||
| .ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| PLOG_DEBUG << "WebSocketServer created"; | ||
| instances.insert(this); | ||
| PLOG_DEBUG << "WebSocketServer created"; | ||
| instances.insert(this); | ||
| } | ||
@@ -178,4 +180,4 @@ | ||
| { | ||
| PLOG_DEBUG << "Destructor called"; | ||
| doStop(); | ||
| PLOG_DEBUG << "Destructor called"; | ||
| doStop(); | ||
| } | ||
@@ -185,20 +187,20 @@ | ||
| { | ||
| PLOG_DEBUG << "doStop() called"; | ||
| if (mWebSocketServerPtr) | ||
| PLOG_DEBUG << "doStop() called"; | ||
| if (mWebSocketServerPtr) | ||
| { | ||
| PLOG_DEBUG << "Stopping..."; | ||
| try | ||
| { | ||
| PLOG_DEBUG << "Stopping..."; | ||
| try | ||
| { | ||
| mWebSocketServerPtr->stop(); | ||
| mWebSocketServerPtr.reset(); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| std::cerr << std::string("libdatachannel error while closing WebSocketServer: ") + ex.what() << std::endl; | ||
| return; | ||
| } | ||
| mWebSocketServerPtr->stop(); | ||
| mWebSocketServerPtr.reset(); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| std::cerr << std::string("libdatachannel error while closing WebSocketServer: ") + ex.what() << std::endl; | ||
| return; | ||
| } | ||
| } | ||
| mOnClientCallback.reset(); | ||
| instances.erase(this); | ||
| mOnClientCallback.reset(); | ||
| instances.erase(this); | ||
| } | ||
@@ -208,4 +210,4 @@ | ||
| { | ||
| PLOG_DEBUG << "stop() called"; | ||
| doStop(); | ||
| PLOG_DEBUG << "stop() called"; | ||
| doStop(); | ||
| } | ||
@@ -215,19 +217,19 @@ | ||
| { | ||
| PLOG_DEBUG << "port() called"; | ||
| Napi::Env env = info.Env(); | ||
| PLOG_DEBUG << "port() called"; | ||
| Napi::Env env = info.Env(); | ||
| if (!mWebSocketServerPtr) | ||
| { | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| if (!mWebSocketServerPtr) | ||
| { | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| try | ||
| { | ||
| return Napi::Number::New(info.Env(), mWebSocketServerPtr->port()); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("WebSocketServer error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| try | ||
| { | ||
| return Napi::Number::New(info.Env(), mWebSocketServerPtr->port()); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("WebSocketServer error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| } | ||
@@ -237,40 +239,44 @@ | ||
| { | ||
| PLOG_DEBUG << "onClient() called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| PLOG_DEBUG << "onClient() called"; | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (!mWebSocketServerPtr) | ||
| { | ||
| Napi::Error::New(env, "onClient() called on destroyed WebSocketServer").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (!mWebSocketServerPtr) | ||
| { | ||
| Napi::Error::New(env, "onClient() called on destroyed WebSocketServer").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected as onClient callback").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected as onClient callback").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| // Callback | ||
| mOnClientCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| // Callback | ||
| mOnClientCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| mWebSocketServerPtr->onClient([&](std::shared_ptr<rtc::WebSocket> ws) | ||
| { | ||
| PLOG_DEBUG << "onClient ws received from WebSocketServer"; | ||
| if (mOnClientCallback) | ||
| mOnClientCallback->call([this, ws](Napi::Env env, std::vector<napi_value> &args) { | ||
| PLOG_DEBUG << "mOnClientCallback call(1)"; | ||
| // Check the WebSocketServer is not stopped | ||
| if(instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| mWebSocketServerPtr->onClient( | ||
| [&](std::shared_ptr<rtc::WebSocket> ws) | ||
| { | ||
| PLOG_DEBUG << "onClient ws received from WebSocketServer"; | ||
| if (mOnClientCallback) | ||
| mOnClientCallback->call( | ||
| [this, ws](Napi::Env env, std::vector<napi_value> &args) | ||
| { | ||
| PLOG_DEBUG << "mOnClientCallback call(1)"; | ||
| // Check the WebSocketServer is not stopped | ||
| if (instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| std::shared_ptr<rtc::WebSocket> webSocket = ws; | ||
| // First argument is just a placeholder | ||
| auto instance = WebSocketWrapper::constructor.New({Napi::Boolean::New(env, false), Napi::External<std::shared_ptr<rtc::WebSocket>>::New(env, &webSocket)}); | ||
| args = {instance}; | ||
| PLOG_DEBUG << "mOnClientCallback call(2)"; | ||
| }); | ||
| }); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| std::shared_ptr<rtc::WebSocket> webSocket = ws; | ||
| // First argument is just a placeholder | ||
| auto instance = WebSocketWrapper::constructor.New( | ||
| {Napi::Boolean::New(env, false), Napi::External<std::shared_ptr<rtc::WebSocket>>::New(env, &webSocket)}); | ||
| args = {instance}; | ||
| PLOG_DEBUG << "mOnClientCallback call(2)"; | ||
| }); | ||
| }); | ||
| } |
| #ifndef WEB_SOCKET_SERVER_WRAPPER_H | ||
| #define WEB_SOCKET_SERVER_WRAPPER_H | ||
| #include <iostream> | ||
| #include <string> | ||
| #include <variant> | ||
| #include <memory> | ||
@@ -13,3 +10,2 @@ #include <unordered_set> | ||
| #include "web-socket-wrapper.h" | ||
| #include "thread-safe-callback.h" | ||
@@ -16,0 +12,0 @@ |
+580
-553
@@ -10,6 +10,6 @@ #include "web-socket-wrapper.h" | ||
| { | ||
| PLOG_DEBUG << "CloseAll() called"; | ||
| auto copy(instances); | ||
| for (auto inst : copy) | ||
| inst->doClose(); | ||
| PLOG_DEBUG << "CloseAll() called"; | ||
| auto copy(instances); | ||
| for (auto inst : copy) | ||
| inst->doClose(); | ||
| } | ||
@@ -19,6 +19,6 @@ | ||
| { | ||
| PLOG_DEBUG << "CleanupAll() called"; | ||
| auto copy(instances); | ||
| for (auto inst : copy) | ||
| inst->doCleanup(); | ||
| PLOG_DEBUG << "CleanupAll() called"; | ||
| auto copy(instances); | ||
| for (auto inst : copy) | ||
| inst->doCleanup(); | ||
| } | ||
@@ -28,35 +28,34 @@ | ||
| { | ||
| Napi::HandleScope scope(env); | ||
| Napi::HandleScope scope(env); | ||
| Napi::Function func = DefineClass( | ||
| env, | ||
| "WebSocket", | ||
| { | ||
| InstanceMethod("open", &WebSocketWrapper::open), | ||
| InstanceMethod("close", &WebSocketWrapper::close), | ||
| InstanceMethod("forceClose", &WebSocketWrapper::forceClose), | ||
| InstanceMethod("sendMessage", &WebSocketWrapper::sendMessage), | ||
| InstanceMethod("sendMessageBinary", &WebSocketWrapper::sendMessageBinary), | ||
| InstanceMethod("isOpen", &WebSocketWrapper::isOpen), | ||
| InstanceMethod("bufferedAmount", &WebSocketWrapper::bufferedAmount), | ||
| InstanceMethod("maxMessageSize", &WebSocketWrapper::maxMessageSize), | ||
| InstanceMethod("setBufferedAmountLowThreshold", &WebSocketWrapper::setBufferedAmountLowThreshold), | ||
| InstanceMethod("onOpen", &WebSocketWrapper::onOpen), | ||
| InstanceMethod("onClosed", &WebSocketWrapper::onClosed), | ||
| InstanceMethod("onError", &WebSocketWrapper::onError), | ||
| InstanceMethod("onBufferedAmountLow", &WebSocketWrapper::onBufferedAmountLow), | ||
| InstanceMethod("onMessage", &WebSocketWrapper::onMessage), | ||
| InstanceMethod("remoteAddress", &WebSocketWrapper::remoteAddress), | ||
| InstanceMethod("path", &WebSocketWrapper::path), | ||
| }); | ||
| Napi::Function func = | ||
| DefineClass(env, "WebSocket", | ||
| { | ||
| InstanceMethod("open", &WebSocketWrapper::open), | ||
| InstanceMethod("close", &WebSocketWrapper::close), | ||
| InstanceMethod("forceClose", &WebSocketWrapper::forceClose), | ||
| InstanceMethod("sendMessage", &WebSocketWrapper::sendMessage), | ||
| InstanceMethod("sendMessageBinary", &WebSocketWrapper::sendMessageBinary), | ||
| InstanceMethod("isOpen", &WebSocketWrapper::isOpen), | ||
| InstanceMethod("bufferedAmount", &WebSocketWrapper::bufferedAmount), | ||
| InstanceMethod("maxMessageSize", &WebSocketWrapper::maxMessageSize), | ||
| InstanceMethod("setBufferedAmountLowThreshold", &WebSocketWrapper::setBufferedAmountLowThreshold), | ||
| InstanceMethod("onOpen", &WebSocketWrapper::onOpen), | ||
| InstanceMethod("onClosed", &WebSocketWrapper::onClosed), | ||
| InstanceMethod("onError", &WebSocketWrapper::onError), | ||
| InstanceMethod("onBufferedAmountLow", &WebSocketWrapper::onBufferedAmountLow), | ||
| InstanceMethod("onMessage", &WebSocketWrapper::onMessage), | ||
| InstanceMethod("remoteAddress", &WebSocketWrapper::remoteAddress), | ||
| InstanceMethod("path", &WebSocketWrapper::path), | ||
| }); | ||
| // If this is not the first call, we don't want to reassign the constructor (hot-reload problem) | ||
| if(constructor.IsEmpty()) | ||
| { | ||
| constructor = Napi::Persistent(func); | ||
| constructor.SuppressDestruct(); | ||
| } | ||
| // If this is not the first call, we don't want to reassign the constructor (hot-reload problem) | ||
| if (constructor.IsEmpty()) | ||
| { | ||
| constructor = Napi::Persistent(func); | ||
| constructor.SuppressDestruct(); | ||
| } | ||
| exports.Set("WebSocket", func); | ||
| return exports; | ||
| exports.Set("WebSocket", func); | ||
| return exports; | ||
| } | ||
@@ -66,195 +65,201 @@ | ||
| { | ||
| PLOG_DEBUG << "Constructor called"; | ||
| Napi::Env env = info.Env(); | ||
| PLOG_DEBUG << "Constructor called"; | ||
| Napi::Env env = info.Env(); | ||
| // Create WebSocket using rtc::WebSocket provided by WebSocketServer | ||
| if (info.Length() > 1) | ||
| { | ||
| mWebSocketPtr = *(info[1].As<Napi::External<std::shared_ptr<rtc::WebSocket>>>().Data()); | ||
| PLOG_DEBUG << "Using WebSocket got from WebSocketServer"; | ||
| instances.insert(this); | ||
| // Create WebSocket using rtc::WebSocket provided by WebSocketServer | ||
| if (info.Length() > 1) | ||
| { | ||
| mWebSocketPtr = *(info[1].As<Napi::External<std::shared_ptr<rtc::WebSocket>>>().Data()); | ||
| PLOG_DEBUG << "Using WebSocket got from WebSocketServer"; | ||
| instances.insert(this); | ||
| // Closed callback must be set to trigger cleanup | ||
| mOnClosedCallback = std::make_unique<ThreadSafeCallback>(Napi::Function::New(info.Env(), [](const Napi::CallbackInfo &) {})); | ||
| return; | ||
| } | ||
| // Closed callback must be set to trigger cleanup | ||
| mOnClosedCallback = | ||
| std::make_unique<ThreadSafeCallback>(Napi::Function::New(info.Env(), [](const Napi::CallbackInfo &) {})); | ||
| return; | ||
| } | ||
| // Create WebSocket without config | ||
| if (info.Length() == 0) | ||
| // Create WebSocket without config | ||
| if (info.Length() == 0) | ||
| { | ||
| try | ||
| { | ||
| try | ||
| { | ||
| PLOG_DEBUG << "Creating a new WebSocket without config"; | ||
| mWebSocketPtr = std::make_unique<rtc::WebSocket>(); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error while creating WebSocket without config: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| instances.insert(this); | ||
| // Closed callback must be set to trigger cleanup | ||
| mOnClosedCallback = std::make_unique<ThreadSafeCallback>(Napi::Function::New(info.Env(), [](const Napi::CallbackInfo &) {})); | ||
| return; | ||
| PLOG_DEBUG << "Creating a new WebSocket without config"; | ||
| mWebSocketPtr = std::make_unique<rtc::WebSocket>(); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error while creating WebSocket without config: ") + ex.what()) | ||
| .ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| instances.insert(this); | ||
| // Create WebSocket with config | ||
| PLOG_DEBUG << "Creating a new WebSocket with config"; | ||
| // Closed callback must be set to trigger cleanup | ||
| mOnClosedCallback = | ||
| std::make_unique<ThreadSafeCallback>(Napi::Function::New(info.Env(), [](const Napi::CallbackInfo &) {})); | ||
| return; | ||
| } | ||
| Napi::Object config = info[0].As<Napi::Object>(); | ||
| rtc::WebSocketConfiguration webSocketConfig; | ||
| // Create WebSocket with config | ||
| PLOG_DEBUG << "Creating a new WebSocket with config"; | ||
| if (config.Has("disableTlsVerification")) | ||
| Napi::Object config = info[0].As<Napi::Object>(); | ||
| rtc::WebSocketConfiguration webSocketConfig; | ||
| if (config.Has("disableTlsVerification")) | ||
| { | ||
| if (!config.Get("disableTlsVerification").IsBoolean()) | ||
| { | ||
| if (!config.Get("disableTlsVerification").IsBoolean()) | ||
| { | ||
| Napi::TypeError::New(info.Env(), "disableTlsVerification must be boolean").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| webSocketConfig.disableTlsVerification = config.Get("disableTlsVerification").ToBoolean(); | ||
| Napi::TypeError::New(info.Env(), "disableTlsVerification must be boolean").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| webSocketConfig.disableTlsVerification = config.Get("disableTlsVerification").ToBoolean(); | ||
| } | ||
| // Proxy Server | ||
| if (config.Has("proxyServer") && config.Get("proxyServer").IsObject()) | ||
| { | ||
| Napi::Object proxyServer = config.Get("proxyServer").As<Napi::Object>(); | ||
| // Proxy Server | ||
| if (config.Has("proxyServer") && config.Get("proxyServer").IsObject()) | ||
| { | ||
| Napi::Object proxyServer = config.Get("proxyServer").As<Napi::Object>(); | ||
| // IP | ||
| std::string ip = proxyServer.Get("ip").As<Napi::String>(); | ||
| // IP | ||
| std::string ip = proxyServer.Get("ip").As<Napi::String>(); | ||
| // Port | ||
| uint16_t port = proxyServer.Get("port").As<Napi::Number>().Uint32Value(); | ||
| // Port | ||
| uint16_t port = proxyServer.Get("port").As<Napi::Number>().Uint32Value(); | ||
| // Type | ||
| std::string strType = proxyServer.Get("type").As<Napi::String>().ToString(); | ||
| rtc::ProxyServer::Type type = rtc::ProxyServer::Type::Http; | ||
| // Type | ||
| std::string strType = proxyServer.Get("type").As<Napi::String>().ToString(); | ||
| rtc::ProxyServer::Type type = rtc::ProxyServer::Type::Http; | ||
| if (strType == "Socks5") | ||
| type = rtc::ProxyServer::Type::Socks5; | ||
| if (strType == "Socks5") | ||
| type = rtc::ProxyServer::Type::Socks5; | ||
| // Username & Password | ||
| std::string username = ""; | ||
| std::string password = ""; | ||
| // Username & Password | ||
| std::string username = ""; | ||
| std::string password = ""; | ||
| if (proxyServer.Get("username").IsString()) | ||
| username = proxyServer.Get("username").As<Napi::String>().ToString(); | ||
| if (proxyServer.Get("password").IsString()) | ||
| password = proxyServer.Get("password").As<Napi::String>().ToString(); | ||
| if (proxyServer.Get("username").IsString()) | ||
| username = proxyServer.Get("username").As<Napi::String>().ToString(); | ||
| if (proxyServer.Get("password").IsString()) | ||
| password = proxyServer.Get("password").As<Napi::String>().ToString(); | ||
| webSocketConfig.proxyServer = rtc::ProxyServer(type, ip, port, username, password); | ||
| } | ||
| webSocketConfig.proxyServer = rtc::ProxyServer(type, ip, port, username, password); | ||
| } | ||
| if (config.Has("protocols")) | ||
| if (config.Has("protocols")) | ||
| { | ||
| if (!config.Get("protocols").IsArray()) | ||
| { | ||
| if (!config.Get("protocols").IsArray()) | ||
| { | ||
| Napi::TypeError::New(info.Env(), "protocols must be an array").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| Napi::Array protocols = config.Get("protocols").As<Napi::Array>(); | ||
| for (uint32_t i = 0; i < protocols.Length(); i++) | ||
| { | ||
| webSocketConfig.protocols.push_back(protocols.Get(i).ToString()); | ||
| } | ||
| Napi::TypeError::New(info.Env(), "protocols must be an array").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (config.Has("connectionTimeout")) | ||
| Napi::Array protocols = config.Get("protocols").As<Napi::Array>(); | ||
| for (uint32_t i = 0; i < protocols.Length(); i++) | ||
| { | ||
| if (!config.Get("connectionTimeout").IsNumber()) | ||
| { | ||
| Napi::TypeError::New(info.Env(), "connectionTimeout must be a number").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| webSocketConfig.connectionTimeout = std::chrono::milliseconds(config.Get("connectionTimeout").ToNumber().Int64Value()); | ||
| webSocketConfig.protocols.push_back(protocols.Get(i).ToString()); | ||
| } | ||
| } | ||
| if (config.Has("pingInterval")) | ||
| if (config.Has("connectionTimeout")) | ||
| { | ||
| if (!config.Get("connectionTimeout").IsNumber()) | ||
| { | ||
| if (!config.Get("pingInterval").IsNumber()) | ||
| { | ||
| Napi::TypeError::New(info.Env(), "pingInterval must be a number").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| webSocketConfig.pingInterval = std::chrono::milliseconds(config.Get("pingInterval").ToNumber().Int64Value()); | ||
| Napi::TypeError::New(info.Env(), "connectionTimeout must be a number").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| webSocketConfig.connectionTimeout = | ||
| std::chrono::milliseconds(config.Get("connectionTimeout").ToNumber().Int64Value()); | ||
| } | ||
| if (config.Has("maxOutstandingPings")) | ||
| if (config.Has("pingInterval")) | ||
| { | ||
| if (!config.Get("pingInterval").IsNumber()) | ||
| { | ||
| if (!config.Get("maxOutstandingPings").IsNumber()) | ||
| { | ||
| Napi::TypeError::New(info.Env(), "maxOutstandingPings must be a number").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| webSocketConfig.maxOutstandingPings = config.Get("maxOutstandingPings").ToNumber().Int32Value(); | ||
| Napi::TypeError::New(info.Env(), "pingInterval must be a number").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| webSocketConfig.pingInterval = std::chrono::milliseconds(config.Get("pingInterval").ToNumber().Int64Value()); | ||
| } | ||
| if (config.Has("caCertificatePemFile")) | ||
| if (config.Has("maxOutstandingPings")) | ||
| { | ||
| if (!config.Get("maxOutstandingPings").IsNumber()) | ||
| { | ||
| if (!config.Get("caCertificatePemFile").IsString()) | ||
| { | ||
| Napi::TypeError::New(info.Env(), "caCertificatePemFile must be a string").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| webSocketConfig.caCertificatePemFile = config.Get("caCertificatePemFile").ToString(); | ||
| Napi::TypeError::New(info.Env(), "maxOutstandingPings must be a number").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| webSocketConfig.maxOutstandingPings = config.Get("maxOutstandingPings").ToNumber().Int32Value(); | ||
| } | ||
| if (config.Has("certificatePemFile")) | ||
| if (config.Has("caCertificatePemFile")) | ||
| { | ||
| if (!config.Get("caCertificatePemFile").IsString()) | ||
| { | ||
| if (!config.Get("certificatePemFile").IsString()) | ||
| { | ||
| Napi::TypeError::New(info.Env(), "certificatePemFile must be a string").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| webSocketConfig.certificatePemFile = config.Get("certificatePemFile").ToString(); | ||
| Napi::TypeError::New(info.Env(), "caCertificatePemFile must be a string").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| webSocketConfig.caCertificatePemFile = config.Get("caCertificatePemFile").ToString(); | ||
| } | ||
| if (config.Has("keyPemFile")) | ||
| if (config.Has("certificatePemFile")) | ||
| { | ||
| if (!config.Get("certificatePemFile").IsString()) | ||
| { | ||
| if (!config.Get("keyPemFile").IsString()) | ||
| { | ||
| Napi::TypeError::New(info.Env(), "keyPemFile must be a string").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| webSocketConfig.keyPemFile = config.Get("keyPemFile").ToString(); | ||
| Napi::TypeError::New(info.Env(), "certificatePemFile must be a string").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| webSocketConfig.certificatePemFile = config.Get("certificatePemFile").ToString(); | ||
| } | ||
| if (config.Has("keyPemPass")) | ||
| if (config.Has("keyPemFile")) | ||
| { | ||
| if (!config.Get("keyPemFile").IsString()) | ||
| { | ||
| if (!config.Get("keyPemPass").IsString()) | ||
| { | ||
| Napi::TypeError::New(info.Env(), "keyPemPass must be a string").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| webSocketConfig.keyPemPass = config.Get("keyPemPass").ToString(); | ||
| Napi::TypeError::New(info.Env(), "keyPemFile must be a string").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| webSocketConfig.keyPemFile = config.Get("keyPemFile").ToString(); | ||
| } | ||
| if (config.Has("maxMessageSize")) | ||
| if (config.Has("keyPemPass")) | ||
| { | ||
| if (!config.Get("keyPemPass").IsString()) | ||
| { | ||
| if (!config.Get("maxMessageSize").IsNumber()) | ||
| { | ||
| Napi::TypeError::New(info.Env(), "maxMessageSize must be a number").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| webSocketConfig.maxMessageSize = config.Get("maxMessageSize").ToNumber().Int32Value(); | ||
| Napi::TypeError::New(info.Env(), "keyPemPass must be a string").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| webSocketConfig.keyPemPass = config.Get("keyPemPass").ToString(); | ||
| } | ||
| // Create WebSocket | ||
| try | ||
| if (config.Has("maxMessageSize")) | ||
| { | ||
| if (!config.Get("maxMessageSize").IsNumber()) | ||
| { | ||
| PLOG_DEBUG << "Creating a new WebSocket"; | ||
| mWebSocketPtr = std::make_unique<rtc::WebSocket>(webSocketConfig); | ||
| Napi::TypeError::New(info.Env(), "maxMessageSize must be a number").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error while creating WebSocket: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| webSocketConfig.maxMessageSize = config.Get("maxMessageSize").ToNumber().Int32Value(); | ||
| } | ||
| PLOG_DEBUG << "WebSocket created"; | ||
| // Create WebSocket | ||
| try | ||
| { | ||
| PLOG_DEBUG << "Creating a new WebSocket"; | ||
| mWebSocketPtr = std::make_unique<rtc::WebSocket>(webSocketConfig); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error while creating WebSocket: ") + ex.what()) | ||
| .ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| // Closed callback must set to trigger cleanup | ||
| mOnClosedCallback = std::make_unique<ThreadSafeCallback>(Napi::Function::New(info.Env(), [](const Napi::CallbackInfo &) {})); | ||
| PLOG_DEBUG << "WebSocket created"; | ||
| instances.insert(this); | ||
| // Closed callback must set to trigger cleanup | ||
| mOnClosedCallback = | ||
| std::make_unique<ThreadSafeCallback>(Napi::Function::New(info.Env(), [](const Napi::CallbackInfo &) {})); | ||
| instances.insert(this); | ||
| } | ||
@@ -264,4 +269,4 @@ | ||
| { | ||
| PLOG_DEBUG << "Destructor called"; | ||
| doClose(); | ||
| PLOG_DEBUG << "Destructor called"; | ||
| doClose(); | ||
| } | ||
@@ -271,22 +276,22 @@ | ||
| { | ||
| PLOG_DEBUG << "doClose() called"; | ||
| if (mWebSocketPtr) | ||
| PLOG_DEBUG << "doClose() called"; | ||
| if (mWebSocketPtr) | ||
| { | ||
| PLOG_DEBUG << "Closing..."; | ||
| try | ||
| { | ||
| PLOG_DEBUG << "Closing..."; | ||
| try | ||
| { | ||
| mWebSocketPtr->close(); | ||
| mWebSocketPtr.reset(); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| std::cerr << std::string("libdatachannel error while closing WebSocket: ") + ex.what() << std::endl; | ||
| return; | ||
| } | ||
| mWebSocketPtr->close(); | ||
| mWebSocketPtr.reset(); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| std::cerr << std::string("libdatachannel error while closing WebSocket: ") + ex.what() << std::endl; | ||
| return; | ||
| } | ||
| } | ||
| mOnOpenCallback.reset(); | ||
| mOnErrorCallback.reset(); | ||
| mOnBufferedAmountLowCallback.reset(); | ||
| mOnMessageCallback.reset(); | ||
| mOnOpenCallback.reset(); | ||
| mOnErrorCallback.reset(); | ||
| mOnBufferedAmountLowCallback.reset(); | ||
| mOnMessageCallback.reset(); | ||
| } | ||
@@ -296,22 +301,22 @@ | ||
| { | ||
| PLOG_DEBUG << "doForceClose() called"; | ||
| if (mWebSocketPtr) | ||
| PLOG_DEBUG << "doForceClose() called"; | ||
| if (mWebSocketPtr) | ||
| { | ||
| PLOG_DEBUG << "Force closing..."; | ||
| try | ||
| { | ||
| PLOG_DEBUG << "Force closing..."; | ||
| try | ||
| { | ||
| mWebSocketPtr->forceClose(); | ||
| mWebSocketPtr.reset(); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| std::cerr << std::string("libdatachannel error while force closing WebSocket: ") + ex.what() << std::endl; | ||
| return; | ||
| } | ||
| mWebSocketPtr->forceClose(); | ||
| mWebSocketPtr.reset(); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| std::cerr << std::string("libdatachannel error while force closing WebSocket: ") + ex.what() << std::endl; | ||
| return; | ||
| } | ||
| } | ||
| mOnOpenCallback.reset(); | ||
| mOnErrorCallback.reset(); | ||
| mOnBufferedAmountLowCallback.reset(); | ||
| mOnMessageCallback.reset(); | ||
| mOnOpenCallback.reset(); | ||
| mOnErrorCallback.reset(); | ||
| mOnBufferedAmountLowCallback.reset(); | ||
| mOnMessageCallback.reset(); | ||
| } | ||
@@ -321,5 +326,5 @@ | ||
| { | ||
| PLOG_DEBUG << "doCleanup() called"; | ||
| mOnClosedCallback.reset(); | ||
| instances.erase(this); | ||
| PLOG_DEBUG << "doCleanup() called"; | ||
| mOnClosedCallback.reset(); | ||
| instances.erase(this); | ||
| } | ||
@@ -329,25 +334,26 @@ | ||
| { | ||
| PLOG_DEBUG << "open() called"; | ||
| Napi::Env env = info.Env(); | ||
| PLOG_DEBUG << "open() called"; | ||
| Napi::Env env = info.Env(); | ||
| if (!mWebSocketPtr) | ||
| { | ||
| Napi::Error::New(env, "open() called on destroyed WebSocket").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (info.Length() < 1 || !info[0].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "url must be string").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (!mWebSocketPtr) | ||
| { | ||
| Napi::Error::New(env, "open() called on destroyed WebSocket").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (info.Length() < 1 || !info[0].IsString()) | ||
| { | ||
| Napi::TypeError::New(env, "url must be string").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| try | ||
| { | ||
| mWebSocketPtr->open(info[0].As<Napi::String>().ToString()); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error while opening WebSocket: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| try | ||
| { | ||
| mWebSocketPtr->open(info[0].As<Napi::String>().ToString()); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error while opening WebSocket: ") + ex.what()) | ||
| .ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| } | ||
@@ -357,4 +363,4 @@ | ||
| { | ||
| PLOG_DEBUG << "close() called"; | ||
| doClose(); | ||
| PLOG_DEBUG << "close() called"; | ||
| doClose(); | ||
| } | ||
@@ -364,4 +370,4 @@ | ||
| { | ||
| PLOG_DEBUG << "forceClose() called"; | ||
| doForceClose(); | ||
| PLOG_DEBUG << "forceClose() called"; | ||
| doForceClose(); | ||
| } | ||
@@ -371,28 +377,29 @@ | ||
| { | ||
| PLOG_DEBUG << "sendMessage() called"; | ||
| if (!mWebSocketPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "sendMessage() called on destroyed channel").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| PLOG_DEBUG << "sendMessage() called"; | ||
| if (!mWebSocketPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "sendMessage() called on destroyed channel").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| // Allow call with NULL | ||
| if (length < 1 || (!info[0].IsString() && !info[0].IsNull())) | ||
| { | ||
| Napi::TypeError::New(env, "String or Null expected").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| // Allow call with NULL | ||
| if (length < 1 || (!info[0].IsString() && !info[0].IsNull())) | ||
| { | ||
| Napi::TypeError::New(env, "String or Null expected").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| try | ||
| { | ||
| return Napi::Boolean::New(info.Env(), mWebSocketPtr->send(info[0].As<Napi::String>().ToString())); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error while sending data channel message: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Boolean::New(info.Env(), false); | ||
| } | ||
| try | ||
| { | ||
| return Napi::Boolean::New(info.Env(), mWebSocketPtr->send(info[0].As<Napi::String>().ToString())); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error while sending data channel message: ") + ex.what()) | ||
| .ThrowAsJavaScriptException(); | ||
| return Napi::Boolean::New(info.Env(), false); | ||
| } | ||
| } | ||
@@ -402,28 +409,29 @@ | ||
| { | ||
| PLOG_DEBUG << "sendMessageBinary() called"; | ||
| if (!mWebSocketPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "sendMessagBinary() called on destroyed channel").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| PLOG_DEBUG << "sendMessageBinary() called"; | ||
| if (!mWebSocketPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "sendMessagBinary() called on destroyed channel").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsBuffer()) | ||
| { | ||
| Napi::TypeError::New(env, "Buffer expected").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| if (length < 1 || !info[0].IsBuffer()) | ||
| { | ||
| Napi::TypeError::New(env, "Buffer expected").ThrowAsJavaScriptException(); | ||
| return info.Env().Null(); | ||
| } | ||
| try | ||
| { | ||
| Napi::Uint8Array buffer = info[0].As<Napi::Uint8Array>(); | ||
| return Napi::Boolean::New(info.Env(), mWebSocketPtr->send((std::byte *)buffer.Data(), buffer.ByteLength())); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error while sending data channel message: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Boolean::New(info.Env(), false); | ||
| } | ||
| try | ||
| { | ||
| Napi::Uint8Array buffer = info[0].As<Napi::Uint8Array>(); | ||
| return Napi::Boolean::New(info.Env(), mWebSocketPtr->send((std::byte *)buffer.Data(), buffer.ByteLength())); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error while sending data channel message: ") + ex.what()) | ||
| .ThrowAsJavaScriptException(); | ||
| return Napi::Boolean::New(info.Env(), false); | ||
| } | ||
| } | ||
@@ -433,19 +441,19 @@ | ||
| { | ||
| PLOG_DEBUG << "isOpen() called"; | ||
| Napi::Env env = info.Env(); | ||
| PLOG_DEBUG << "isOpen() called"; | ||
| Napi::Env env = info.Env(); | ||
| if (!mWebSocketPtr) | ||
| { | ||
| return Napi::Boolean::New(info.Env(), false); | ||
| } | ||
| if (!mWebSocketPtr) | ||
| { | ||
| return Napi::Boolean::New(info.Env(), false); | ||
| } | ||
| try | ||
| { | ||
| return Napi::Boolean::New(info.Env(), mWebSocketPtr->isOpen()); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Boolean::New(info.Env(), false); | ||
| } | ||
| try | ||
| { | ||
| return Napi::Boolean::New(info.Env(), mWebSocketPtr->isOpen()); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Boolean::New(info.Env(), false); | ||
| } | ||
| } | ||
@@ -455,19 +463,19 @@ | ||
| { | ||
| PLOG_DEBUG << "bufferedAmount() called"; | ||
| Napi::Env env = info.Env(); | ||
| PLOG_DEBUG << "bufferedAmount() called"; | ||
| Napi::Env env = info.Env(); | ||
| if (!mWebSocketPtr) | ||
| { | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| if (!mWebSocketPtr) | ||
| { | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| try | ||
| { | ||
| return Napi::Number::New(info.Env(), mWebSocketPtr->bufferedAmount()); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| try | ||
| { | ||
| return Napi::Number::New(info.Env(), mWebSocketPtr->bufferedAmount()); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| } | ||
@@ -477,19 +485,19 @@ | ||
| { | ||
| PLOG_DEBUG << "maxMessageSize() called"; | ||
| Napi::Env env = info.Env(); | ||
| PLOG_DEBUG << "maxMessageSize() called"; | ||
| Napi::Env env = info.Env(); | ||
| if (!mWebSocketPtr) | ||
| { | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| if (!mWebSocketPtr) | ||
| { | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| try | ||
| { | ||
| return Napi::Number::New(info.Env(), mWebSocketPtr->maxMessageSize()); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| try | ||
| { | ||
| return Napi::Number::New(info.Env(), mWebSocketPtr->maxMessageSize()); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return Napi::Number::New(info.Env(), 0); | ||
| } | ||
| } | ||
@@ -499,27 +507,27 @@ | ||
| { | ||
| PLOG_DEBUG << "remoteAddress() called"; | ||
| Napi::Env env = info.Env(); | ||
| PLOG_DEBUG << "remoteAddress() called"; | ||
| Napi::Env env = info.Env(); | ||
| if (!mWebSocketPtr) | ||
| { | ||
| return env.Undefined(); | ||
| } | ||
| if (!mWebSocketPtr) | ||
| { | ||
| return env.Undefined(); | ||
| } | ||
| try | ||
| try | ||
| { | ||
| auto address = mWebSocketPtr->remoteAddress(); | ||
| if (address.has_value()) | ||
| { | ||
| auto address = mWebSocketPtr->remoteAddress(); | ||
| if (address.has_value()) | ||
| { | ||
| return Napi::String::New(info.Env(), address.value()); | ||
| } | ||
| else | ||
| { | ||
| return env.Undefined(); | ||
| } | ||
| return Napi::String::New(info.Env(), address.value()); | ||
| } | ||
| catch (std::exception &ex) | ||
| else | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return env.Undefined(); | ||
| return env.Undefined(); | ||
| } | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return env.Undefined(); | ||
| } | ||
| } | ||
@@ -529,27 +537,27 @@ | ||
| { | ||
| PLOG_DEBUG << "path() called"; | ||
| Napi::Env env = info.Env(); | ||
| PLOG_DEBUG << "path() called"; | ||
| Napi::Env env = info.Env(); | ||
| if (!mWebSocketPtr) | ||
| { | ||
| return env.Undefined(); | ||
| } | ||
| if (!mWebSocketPtr) | ||
| { | ||
| return env.Undefined(); | ||
| } | ||
| try | ||
| try | ||
| { | ||
| auto path = mWebSocketPtr->path(); | ||
| if (path.has_value()) | ||
| { | ||
| auto path = mWebSocketPtr->path(); | ||
| if (path.has_value()) | ||
| { | ||
| return Napi::String::New(info.Env(), path.value()); | ||
| } | ||
| else | ||
| { | ||
| return env.Undefined(); | ||
| } | ||
| return Napi::String::New(info.Env(), path.value()); | ||
| } | ||
| catch (std::exception &ex) | ||
| else | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return env.Undefined(); | ||
| return env.Undefined(); | ||
| } | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return env.Undefined(); | ||
| } | ||
| } | ||
@@ -559,27 +567,28 @@ | ||
| { | ||
| PLOG_DEBUG << "setBufferedAmountLowThreshold() called"; | ||
| if (!mWebSocketPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "setBufferedAmountLowThreshold() called on destroyed channel").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| PLOG_DEBUG << "setBufferedAmountLowThreshold() called"; | ||
| if (!mWebSocketPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "setBufferedAmountLowThreshold() called on destroyed channel") | ||
| .ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "Number expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsNumber()) | ||
| { | ||
| Napi::TypeError::New(env, "Number expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| try | ||
| { | ||
| mWebSocketPtr->setBufferedAmountLowThreshold(info[0].ToNumber().Uint32Value()); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| try | ||
| { | ||
| mWebSocketPtr->setBufferedAmountLowThreshold(info[0].ToNumber().Uint32Value()); | ||
| } | ||
| catch (std::exception &ex) | ||
| { | ||
| Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| } | ||
@@ -589,41 +598,44 @@ | ||
| { | ||
| PLOG_DEBUG << "new onOpen() called"; | ||
| if (!mWebSocketPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "onOpen() called on destroyed channel").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| PLOG_DEBUG << "new onOpen() called"; | ||
| if (!mWebSocketPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "onOpen() called on destroyed channel").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| // Callback | ||
| mOnOpenCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| // Callback | ||
| mOnOpenCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| mWebSocketPtr->onOpen([&]() | ||
| { | ||
| PLOG_DEBUG << "onOpen cb received from rtc"; | ||
| mWebSocketPtr->onOpen( | ||
| [&]() | ||
| { | ||
| PLOG_DEBUG << "onOpen cb received from rtc"; | ||
| if (mOnOpenCallback) | ||
| mOnOpenCallback->call([this](Napi::Env env, std::vector<napi_value> &args) | ||
| { | ||
| PLOG_DEBUG << "mOnOpenCallback call(1)"; | ||
| // Check the WebSocket is not closed | ||
| if(instances.find(this) == instances.end()) | ||
| { | ||
| PLOG_DEBUG << "WebSocket not found in instances"; | ||
| throw ThreadSafeCallback::CancelException(); | ||
| } | ||
| if (mOnOpenCallback) | ||
| mOnOpenCallback->call( | ||
| [this](Napi::Env env, std::vector<napi_value> &args) | ||
| { | ||
| PLOG_DEBUG << "mOnOpenCallback call(1)"; | ||
| // Check the WebSocket is not closed | ||
| if (instances.find(this) == instances.end()) | ||
| { | ||
| PLOG_DEBUG << "WebSocket not found in instances"; | ||
| throw ThreadSafeCallback::CancelException(); | ||
| } | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| args = {}; | ||
| PLOG_DEBUG << "mOnOpenCallback call(2)"; }); }); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| args = {}; | ||
| PLOG_DEBUG << "mOnOpenCallback call(2)"; | ||
| }); | ||
| }); | ||
| } | ||
@@ -633,35 +645,38 @@ | ||
| { | ||
| PLOG_DEBUG << "onClosed() called"; | ||
| if (!mWebSocketPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "onClosed() called on destroyed WebSocket").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| PLOG_DEBUG << "onClosed() called"; | ||
| if (!mWebSocketPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "onClosed() called on destroyed WebSocket").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| // Callback | ||
| mOnClosedCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| // Callback | ||
| mOnClosedCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| mWebSocketPtr->onClosed([&]() | ||
| { | ||
| PLOG_DEBUG << "onClosed cb received from rtc"; | ||
| if (mOnClosedCallback) | ||
| mOnClosedCallback->call([this](Napi::Env env, std::vector<napi_value> &args) { | ||
| PLOG_DEBUG << "mOnClosedCallback call"; | ||
| // Do not check if the data channel has been closed here | ||
| mWebSocketPtr->onClosed( | ||
| [&]() | ||
| { | ||
| PLOG_DEBUG << "onClosed cb received from rtc"; | ||
| if (mOnClosedCallback) | ||
| mOnClosedCallback->call( | ||
| [this](Napi::Env env, std::vector<napi_value> &args) | ||
| { | ||
| PLOG_DEBUG << "mOnClosedCallback call"; | ||
| // Do not check if the data channel has been closed here | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| args = {}; | ||
| },[this]{ | ||
| doCleanup(); | ||
| }); }); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| args = {}; | ||
| }, | ||
| [this] { doCleanup(); }); | ||
| }); | ||
| } | ||
@@ -671,36 +686,40 @@ | ||
| { | ||
| PLOG_DEBUG << "onError() called"; | ||
| if (!mWebSocketPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "onError() called on destroyed channel").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| PLOG_DEBUG << "onError() called"; | ||
| if (!mWebSocketPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "onError() called on destroyed channel").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| // Callback | ||
| mOnErrorCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| // Callback | ||
| mOnErrorCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| mWebSocketPtr->onError([&](std::string error) | ||
| { | ||
| PLOG_DEBUG << "onError cb received from rtc"; | ||
| if (mOnErrorCallback) | ||
| mOnErrorCallback->call([this, error = std::move(error)](Napi::Env env, std::vector<napi_value> &args) { | ||
| PLOG_DEBUG << "mOnErrorCallback call(1)"; | ||
| // Check the data channel is not closed | ||
| if(instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| mWebSocketPtr->onError( | ||
| [&](std::string error) | ||
| { | ||
| PLOG_DEBUG << "onError cb received from rtc"; | ||
| if (mOnErrorCallback) | ||
| mOnErrorCallback->call( | ||
| [this, error = std::move(error)](Napi::Env env, std::vector<napi_value> &args) | ||
| { | ||
| PLOG_DEBUG << "mOnErrorCallback call(1)"; | ||
| // Check the data channel is not closed | ||
| if (instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| args = {Napi::String::New(env, error)}; | ||
| PLOG_DEBUG << "mOnErrorCallback call(2)"; | ||
| }); }); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| args = {Napi::String::New(env, error)}; | ||
| PLOG_DEBUG << "mOnErrorCallback call(2)"; | ||
| }); | ||
| }); | ||
| } | ||
@@ -710,36 +729,40 @@ | ||
| { | ||
| PLOG_DEBUG << "onBufferedAmountLow() called"; | ||
| if (!mWebSocketPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "onBufferedAmountLow() called on destroyed channel").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| PLOG_DEBUG << "onBufferedAmountLow() called"; | ||
| if (!mWebSocketPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "onBufferedAmountLow() called on destroyed channel").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| // Callback | ||
| mOnBufferedAmountLowCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| // Callback | ||
| mOnBufferedAmountLowCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| mWebSocketPtr->onBufferedAmountLow([&]() | ||
| { | ||
| PLOG_DEBUG << "onBufferedAmountLow cb received from rtc"; | ||
| if (mOnBufferedAmountLowCallback) | ||
| mOnBufferedAmountLowCallback->call([this](Napi::Env env, std::vector<napi_value> &args) { | ||
| PLOG_DEBUG << "mOnBufferedAmountLowCallback call(1)"; | ||
| // Check the data channel is not closed | ||
| if(instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| mWebSocketPtr->onBufferedAmountLow( | ||
| [&]() | ||
| { | ||
| PLOG_DEBUG << "onBufferedAmountLow cb received from rtc"; | ||
| if (mOnBufferedAmountLowCallback) | ||
| mOnBufferedAmountLowCallback->call( | ||
| [this](Napi::Env env, std::vector<napi_value> &args) | ||
| { | ||
| PLOG_DEBUG << "mOnBufferedAmountLowCallback call(1)"; | ||
| // Check the data channel is not closed | ||
| if (instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| args = {}; | ||
| PLOG_DEBUG << "mOnBufferedAmountLowCallback call(2)"; | ||
| }); }); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| args = {}; | ||
| PLOG_DEBUG << "mOnBufferedAmountLowCallback call(2)"; | ||
| }); | ||
| }); | ||
| } | ||
@@ -749,45 +772,49 @@ | ||
| { | ||
| PLOG_DEBUG << "onMessage() called"; | ||
| if (!mWebSocketPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "onMessage() called on destroyed channel").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| PLOG_DEBUG << "onMessage() called"; | ||
| if (!mWebSocketPtr) | ||
| { | ||
| Napi::Error::New(info.Env(), "onMessage() called on destroyed channel").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| Napi::Env env = info.Env(); | ||
| int length = info.Length(); | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| if (length < 1 || !info[0].IsFunction()) | ||
| { | ||
| Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); | ||
| return; | ||
| } | ||
| // Callback | ||
| mOnMessageCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| // Callback | ||
| mOnMessageCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>()); | ||
| PLOG_DEBUG << "setting onMessage cb on mWebSocketPtr"; | ||
| mWebSocketPtr->onMessage([&](std::variant<rtc::binary, std::string> message) | ||
| { | ||
| PLOG_DEBUG << "onMessage cb received from rtc"; | ||
| if (mOnMessageCallback) | ||
| mOnMessageCallback->call([this, message = std::move(message)](Napi::Env env, std::vector<napi_value> &args) { | ||
| PLOG_DEBUG << "mOnMessageCallback call(1)"; | ||
| // Check the data channel is not closed | ||
| if(instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| PLOG_DEBUG << "setting onMessage cb on mWebSocketPtr"; | ||
| mWebSocketPtr->onMessage( | ||
| [&](std::variant<rtc::binary, std::string> message) | ||
| { | ||
| PLOG_DEBUG << "onMessage cb received from rtc"; | ||
| if (mOnMessageCallback) | ||
| mOnMessageCallback->call( | ||
| [this, message = std::move(message)](Napi::Env env, std::vector<napi_value> &args) | ||
| { | ||
| PLOG_DEBUG << "mOnMessageCallback call(1)"; | ||
| // Check the data channel is not closed | ||
| if (instances.find(this) == instances.end()) | ||
| throw ThreadSafeCallback::CancelException(); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| if (std::holds_alternative<std::string>(message)) | ||
| { | ||
| args = {Napi::String::New(env, std::get<std::string>(message))}; | ||
| } | ||
| else | ||
| { | ||
| auto bin = std::get<rtc::binary>(std::move(message)); | ||
| args = {Napi::Buffer<std::byte>::Copy(env, bin.data(), bin.size())}; | ||
| } | ||
| PLOG_DEBUG << "mOnMessageCallback call(2)"; | ||
| }); }); | ||
| // This will run in main thread and needs to construct the | ||
| // arguments for the call | ||
| if (std::holds_alternative<std::string>(message)) | ||
| { | ||
| args = {Napi::String::New(env, std::get<std::string>(message))}; | ||
| } | ||
| else | ||
| { | ||
| auto bin = std::get<rtc::binary>(std::move(message)); | ||
| args = {Napi::Buffer<std::byte>::Copy(env, bin.data(), bin.size())}; | ||
| } | ||
| PLOG_DEBUG << "mOnMessageCallback call(2)"; | ||
| }); | ||
| }); | ||
| } |
| #ifndef WEB_SOCKET_WRAPPER_H | ||
| #define WEB_SOCKET_WRAPPER_H | ||
| #include <iostream> | ||
| #include <string> | ||
| #include <variant> | ||
| #include <memory> | ||
@@ -8,0 +5,0 @@ #include <unordered_set> |
| /* eslint-disable @typescript-eslint/no-explicit-any */ | ||
| import { SelectedCandidateInfo } from '../lib/types'; | ||
| import { DataChannelInitConfig, SelectedCandidateInfo } from '../lib/types'; | ||
| import { PeerConnection } from '../lib/index'; | ||
@@ -39,3 +39,3 @@ import RTCSessionDescription from './RTCSessionDescription'; | ||
| // For ondatachannel we need to define type manually | ||
| ondatachannel: ((this: globalThis.RTCPeerConnection, ev: RTCDataChannelEvent) => any) | null; | ||
| ondatachannel: ((this: globalThis.RTCPeerConnection, ev: globalThis.RTCDataChannelEvent) => any) | null; | ||
| onicecandidate: globalThis.RTCPeerConnection['onicecandidate'] = null; | ||
@@ -366,3 +366,9 @@ onicecandidateerror: globalThis.RTCPeerConnection['onicecandidateerror'] = null; | ||
| createDataChannel(label: string, opts: globalThis.RTCDataChannelInit = {}): RTCDataChannel { | ||
| const channel = this.#peerConnection.createDataChannel(label, opts); | ||
| const nativeOpts: DataChannelInitConfig = { | ||
| ...opts, | ||
| // libdatachannel uses "unordered", opposite of RTCDataChannelInit.ordered | ||
| unordered: opts.ordered === false ? true : false, | ||
| }; | ||
| const channel = this.#peerConnection.createDataChannel(label, nativeOpts); | ||
| const dataChannel = new RTCDataChannel(channel, opts); | ||
@@ -369,0 +375,0 @@ |
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 1 instance in 1 package
149
0.68%5254
0.27%0
-100%608085
-1.15%