@viamrobotics/rpc
Advanced tools
Comparing version 0.1.6 to 0.1.7
{ | ||
"name": "@viamrobotics/rpc", | ||
"version": "0.1.6", | ||
"version": "0.1.7", | ||
"license": "MIT", | ||
@@ -10,2 +10,3 @@ "dependencies": { | ||
"devDependencies": { | ||
"set-value": ">=4.0.1", | ||
"@types/google-protobuf": "^3.7.4", | ||
@@ -12,0 +13,0 @@ "ts-loader": "^8.0.14", |
@@ -135,8 +135,10 @@ import { grpc } from "@improbable-eng/grpc-web"; | ||
let statusCode, statusMessage; | ||
if (trailers.getStatus()) { | ||
statusCode = trailers.getStatus().getCode(); | ||
statusMessage = trailers.getStatus().getMessage(); | ||
headers.set("grpc-status", trailers.getStatus().getCode()); | ||
// this okay? | ||
headers.set("grpc-message", trailers.getStatus().getMessage()); | ||
const status = trailers.getStatus(); | ||
if (status) { | ||
statusCode = status.getCode(); | ||
statusMessage = status.getMessage(); | ||
headers.set("grpc-status", `${status.getCode()}`); | ||
if (statusMessage !== undefined) { | ||
headers.set("grpc-message", status.getMessage()); | ||
} | ||
} else { | ||
@@ -143,0 +145,0 @@ statusCode = 0; |
import { ClientChannel } from "./ClientChannel"; | ||
export declare function dial(signalingAddress: string, host: string, rtcConfig?: RTCConfiguration): Promise<ClientChannel>; | ||
interface DialOptions { | ||
disableTrickleICE: boolean; | ||
rtcConfig?: RTCConfiguration; | ||
} | ||
export declare function dial(signalingAddress: string, host: string, opts?: DialOptions): Promise<ClientChannel>; | ||
export {}; |
230
src/dial.ts
import { grpc } from "@improbable-eng/grpc-web"; | ||
import { CallRequest, CallResponse } from "proto/rpc/webrtc/v1/signaling_pb"; | ||
import { CallRequest, CallResponse, CallUpdateRequest, CallUpdateResponse, ICECandidate } from "proto/rpc/webrtc/v1/signaling_pb"; | ||
import { SignalingService } from "proto/rpc/webrtc/v1/signaling_pb_service"; | ||
import { ClientChannel } from "./ClientChannel"; | ||
import { newPeerConnectionForClient } from "./peer"; | ||
import { Code } from "google-rpc/code_pb" | ||
import { Status } from "google-rpc/status_pb" | ||
async function signalCall(signalingAddress: string, host: string, sdp: string): Promise<CallResponse> { | ||
const callRequest = new CallRequest(); | ||
callRequest.setSdp(sdp); | ||
let pResolve: (value: CallResponse) => void; | ||
interface DialOptions { | ||
disableTrickleICE: boolean; | ||
rtcConfig?: RTCConfiguration; | ||
} | ||
// TODO(https://github.com/viamrobotics/core/issues/111): figure out decent way to handle reconnect on connection termination | ||
export async function dial(signalingAddress: string, host: string, opts?: DialOptions): Promise<ClientChannel> { | ||
const { pc, dc } = await newPeerConnectionForClient(opts !== undefined && opts.disableTrickleICE, opts?.rtcConfig); | ||
const client = grpc.client(SignalingService.Call, { | ||
host: signalingAddress | ||
}) | ||
let uuid = ''; | ||
// only send once since exchange may end or ICE may end | ||
let sentDoneOrErrorOnce = false; | ||
const sendError = (err: string) => { | ||
if (sentDoneOrErrorOnce) { | ||
return; | ||
} | ||
sentDoneOrErrorOnce = true; | ||
const callRequestUpdate = new CallUpdateRequest(); | ||
callRequestUpdate.setUuid(uuid); | ||
const status = new Status(); | ||
status.setCode(Code.UNKNOWN); | ||
status.setMessage(err); | ||
callRequestUpdate.setError(status); | ||
grpc.unary(SignalingService.CallUpdate, { | ||
request: callRequestUpdate, | ||
metadata: { | ||
'rpc-host': host, | ||
}, | ||
host: signalingAddress, | ||
onEnd: (output: grpc.UnaryOutput<CallUpdateResponse>) => { | ||
const { status, statusMessage, message } = output; | ||
if (status === grpc.Code.OK && message) { | ||
return; | ||
} | ||
console.error(statusMessage) | ||
} | ||
}); | ||
} | ||
const sendDone = () => { | ||
if (sentDoneOrErrorOnce) { | ||
return; | ||
} | ||
sentDoneOrErrorOnce = true; | ||
const callRequestUpdate = new CallUpdateRequest(); | ||
callRequestUpdate.setUuid(uuid); | ||
callRequestUpdate.setDone(true); | ||
grpc.unary(SignalingService.CallUpdate, { | ||
request: callRequestUpdate, | ||
metadata: { | ||
'rpc-host': host, | ||
}, | ||
host: signalingAddress, | ||
onEnd: (output: grpc.UnaryOutput<CallUpdateResponse>) => { | ||
const { status, statusMessage, message } = output; | ||
if (status === grpc.Code.OK && message) { | ||
return; | ||
} | ||
console.error(statusMessage) | ||
} | ||
}); | ||
} | ||
let pResolve: (value: any) => void; | ||
let pReject: (reason?: any) => void; | ||
const result = new Promise<CallResponse>((resolve, reject) => { | ||
let remoteDescSet = new Promise<any>((resolve, reject) => { | ||
pResolve = resolve; | ||
pReject = reject; | ||
}) | ||
grpc.unary(SignalingService.Call, { | ||
request: callRequest, | ||
metadata: { | ||
'rpc-host': host, | ||
}, | ||
host: signalingAddress, | ||
onEnd: (output: grpc.UnaryOutput<CallResponse>) => { | ||
const { status, statusMessage, message } = output; | ||
if (status === grpc.Code.OK && message) { | ||
pResolve(message); | ||
} else { | ||
pReject(statusMessage); | ||
}); | ||
let exchangeDone = false; | ||
if (!opts?.disableTrickleICE) { | ||
// set up offer | ||
const offerDesc = await pc.createOffer(); | ||
let iceComplete = false; | ||
pc.onicecandidate = async event => { | ||
await remoteDescSet; | ||
if (exchangeDone) { | ||
return; | ||
} | ||
if (event.candidate === null) { | ||
iceComplete = true; | ||
sendDone(); | ||
return; | ||
} | ||
const iProto = iceCandidateToProto(event.candidate); | ||
const callRequestUpdate = new CallUpdateRequest(); | ||
callRequestUpdate.setUuid(uuid); | ||
callRequestUpdate.setCandidate(iProto); | ||
grpc.unary(SignalingService.CallUpdate, { | ||
request: callRequestUpdate, | ||
metadata: { | ||
'rpc-host': host, | ||
}, | ||
host: signalingAddress, | ||
onEnd: (output: grpc.UnaryOutput<CallUpdateResponse>) => { | ||
const { status, statusMessage, message } = output; | ||
if (status === grpc.Code.OK && message) { | ||
return; | ||
} | ||
if (exchangeDone || iceComplete) { | ||
return; | ||
} | ||
console.error("error sending candidate", statusMessage) | ||
} | ||
}); | ||
} | ||
await pc.setLocalDescription(offerDesc); | ||
} | ||
let haveInit = false; | ||
client.onMessage(async (message: CallResponse) => { | ||
if (message.hasInit()) { | ||
if (haveInit) { | ||
sendError("got init stage more than once"); | ||
return; | ||
} | ||
const init = message.getInit()!; | ||
haveInit = true; | ||
uuid = message.getUuid(); | ||
const remoteSDP = new RTCSessionDescription(JSON.parse(atob(init.getSdp()))); | ||
pc.setRemoteDescription(remoteSDP); | ||
pResolve(true); | ||
if (opts?.disableTrickleICE) { | ||
exchangeDone = true; | ||
sendDone(); | ||
return; | ||
} | ||
} else if (message.hasUpdate()) { | ||
if (!haveInit) { | ||
sendError("got update stage before init stage"); | ||
return; | ||
} | ||
if (message.getUuid() !== uuid) { | ||
sendError(`uuid mismatch; have=${message.getUuid()} want=${uuid}`); | ||
return; | ||
} | ||
const update = message.getUpdate()!; | ||
const cand = iceCandidateFromProto(update.getCandidate()!); | ||
try { | ||
await pc.addIceCandidate(cand); | ||
} catch (e) { | ||
sendError(e); | ||
return; | ||
} | ||
} else { | ||
sendError("unknown CallResponse stage"); | ||
return; | ||
} | ||
}); | ||
return await result; | ||
} | ||
// TODO(https://github.com/viamrobotics/core/issues/111): figure out decent way to handle reconnect on connection termination | ||
export async function dial(signalingAddress: string, host: string, rtcConfig?: RTCConfiguration): Promise<ClientChannel> { | ||
const { pc, dc } = await newPeerConnectionForClient(rtcConfig); | ||
client.onEnd((status: grpc.Code, statusMessage: string, trailers: grpc.Metadata) => { | ||
if (status === grpc.Code.OK) { | ||
return; | ||
} | ||
console.error(statusMessage); | ||
}); | ||
client.start({ 'rpc-host': host }) | ||
const callRequest = new CallRequest(); | ||
const encodedSDP = btoa(JSON.stringify(pc.localDescription)); | ||
const callResponse = await signalCall(signalingAddress, host, encodedSDP); | ||
const remoteSDP = new RTCSessionDescription(JSON.parse(atob(callResponse.getSdp()))); | ||
pc.setRemoteDescription(remoteSDP); | ||
callRequest.setSdp(encodedSDP); | ||
if (opts && opts.disableTrickleICE) { | ||
callRequest.setDisableTrickle(opts.disableTrickleICE); | ||
} | ||
client.send(callRequest); | ||
const cc = new ClientChannel(pc, dc); | ||
await cc.ready; | ||
exchangeDone = true; | ||
sendDone(); | ||
return cc; | ||
} | ||
function iceCandidateFromProto(i: ICECandidate): RTCIceCandidateInit { | ||
let candidate: RTCIceCandidateInit = { | ||
candidate: i.getCandidate(), | ||
} | ||
if (i.hasSdpMid()) { | ||
candidate.sdpMid = i.getSdpMid(); | ||
} | ||
if (i.hasSdpmLineIndex()) { | ||
candidate.sdpMLineIndex = i.getSdpmLineIndex(); | ||
} | ||
if (i.hasUsernameFragment()) { | ||
candidate.usernameFragment = i.getUsernameFragment(); | ||
} | ||
return candidate; | ||
} | ||
function iceCandidateToProto(i: RTCIceCandidateInit): ICECandidate { | ||
let candidate = new ICECandidate(); | ||
candidate.setCandidate(i.candidate!); | ||
if (i.sdpMid) { | ||
candidate.setSdpMid(i.sdpMid); | ||
} | ||
if (i.sdpMLineIndex) { | ||
candidate.setSdpmLineIndex(i.sdpMLineIndex); | ||
} | ||
if (i.usernameFragment) { | ||
candidate.setUsernameFragment(i.usernameFragment); | ||
} | ||
return candidate; | ||
} | ||
@@ -5,3 +5,3 @@ interface ReadyPeer { | ||
} | ||
export declare function newPeerConnectionForClient(rtcConfig?: RTCConfiguration): Promise<ReadyPeer>; | ||
export declare function newPeerConnectionForClient(disableTrickle: boolean, rtcConfig?: RTCConfiguration): Promise<ReadyPeer>; | ||
export {}; |
@@ -6,3 +6,3 @@ interface ReadyPeer { | ||
export async function newPeerConnectionForClient(rtcConfig?: RTCConfiguration): Promise<ReadyPeer> { | ||
export async function newPeerConnectionForClient(disableTrickle: boolean, rtcConfig?: RTCConfiguration): Promise<ReadyPeer> { | ||
if (!rtcConfig) { | ||
@@ -30,2 +30,13 @@ rtcConfig = { | ||
if (!disableTrickle) { | ||
return Promise.resolve({ pc: peerConnection, dc: dataChannel }) | ||
} | ||
// set up offer | ||
const offerDesc = await peerConnection.createOffer(); | ||
try { | ||
await peerConnection.setLocalDescription(offerDesc) | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
peerConnection.onicecandidate = async event => { | ||
@@ -38,10 +49,3 @@ if (event.candidate !== null) { | ||
// set up offer | ||
const offerDesc = await peerConnection.createOffer(); | ||
try { | ||
peerConnection.setLocalDescription(offerDesc) | ||
} catch (e) { | ||
console.error(e); | ||
} | ||
return result; | ||
} |
@@ -22,5 +22,6 @@ { | ||
"paths": { | ||
"proto/*": ["../../../dist/js/proto/*", "../../dist/js/proto/*"] | ||
"proto/*": ["../../../dist/js/proto/*", "../../dist/js/proto/*"], | ||
"google-rpc/*": ["../../../dist/js/google/rpc/*", "../../dist/js/google/rpc/*"] | ||
} | ||
} | ||
} |
@@ -15,2 +15,3 @@ const webpack = require('webpack'); | ||
aliases["proto"] = path.resolve(__dirname, '../../dist/js/proto'); | ||
aliases["google-rpc"] = path.resolve(__dirname, '../../dist/js/google/rpc'); | ||
@@ -29,3 +30,3 @@ module.exports = { | ||
test: /\.ts$/, | ||
include: /src|proto/, | ||
include: /src|proto|google-rpc/, | ||
exclude: /node_modules/, | ||
@@ -32,0 +33,0 @@ loader: "ts-loader" |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
2631377
2903
7