Static Webview - RTC - UDP - TCP - WSS

This commit is contained in:
2026-10-03 01:22:41 -07:00
parent 058104d93f
commit 75224df058
19 changed files with 1994 additions and 284 deletions

607
Sources/rtc_v8_bindings.cpp Normal file
View File

@ -0,0 +1,607 @@
#include "rtc_v8_bindings.h"
#include "rtc_bridge.h"
#include <kinc/log.h>
#include <algorithm>
#include <cstring>
#include <map>
#include <set>
#include <vector>
using namespace v8;
static Isolate *g_rtc_isolate = nullptr;
static std::map<int, Persistent<Function>> &g_desc_cbs = *new std::map<int, Persistent<Function>>;
static std::map<int, Persistent<Function>> &g_cand_cbs = *new std::map<int, Persistent<Function>>;
static std::map<int, Persistent<Function>> &g_state_cbs = *new std::map<int, Persistent<Function>>;
static std::map<int, Persistent<Function>> &g_gathering_cbs = *new std::map<int, Persistent<Function>>;
static std::map<int, Persistent<Function>> &g_dc_cbs = *new std::map<int, Persistent<Function>>;
static std::map<int, Persistent<Function>> &g_open_cbs = *new std::map<int, Persistent<Function>>;
static std::map<int, Persistent<Function>> &g_closed_cbs = *new std::map<int, Persistent<Function>>;
static std::map<int, Persistent<Function>> &g_error_cbs = *new std::map<int, Persistent<Function>>;
static std::map<int, Persistent<Function>> &g_msg_cbs = *new std::map<int, Persistent<Function>>;
static std::map<int, std::vector<int>> &g_pc_dcs = *new std::map<int, std::vector<int>>;
static std::map<int, int> &g_dc_to_pc = *new std::map<int, int>;
static Global<Context> *g_rtc_context = nullptr;
static void rtc_throw(Isolate *isolate, const char *msg) {
isolate->ThrowException(
Exception::TypeError(String::NewFromUtf8(isolate, msg).ToLocalChecked()));
}
static bool rtc_store_cb(const FunctionCallbackInfo<Value> &args,
std::map<int, Persistent<Function>> &cbs,
const char *name) {
Isolate *isolate = args.GetIsolate();
if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsFunction()) {
rtc_throw(isolate, name);
return false;
}
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
cbs[id].Reset(isolate, args[1].As<Function>());
return true;
}
static void rtc_drop_cbs(int id) {
g_desc_cbs.erase(id);
g_cand_cbs.erase(id);
g_state_cbs.erase(id);
g_gathering_cbs.erase(id);
g_dc_cbs.erase(id);
g_open_cbs.erase(id);
g_closed_cbs.erase(id);
g_error_cbs.erase(id);
g_msg_cbs.erase(id);
}
static void rtc_track_dc(int pc, int dc) {
g_pc_dcs[pc].push_back(dc);
g_dc_to_pc[dc] = pc;
}
static void rtc_forget_dc(int dc) {
auto it = g_dc_to_pc.find(dc);
if (it == g_dc_to_pc.end()) return;
auto &dcs = g_pc_dcs[it->second];
dcs.erase(std::remove(dcs.begin(), dcs.end(), dc), dcs.end());
g_dc_to_pc.erase(it);
}
static void rtc_call(Persistent<Function> &cb, int argc, Local<Value> *argv) {
if (cb.IsEmpty()) return;
Isolate *isolate = g_rtc_isolate;
Local<Context> ctx = isolate->GetCurrentContext();
Local<Function> fn = cb.Get(isolate);
TryCatch try_catch(isolate);
fn->Call(ctx, Null(isolate), argc, argv).IsEmpty();
if (try_catch.HasCaught()) {
String::Utf8Value err(isolate, try_catch.Exception());
kinc_log(KINC_LOG_LEVEL_WARNING, "runt_rtc callback threw: %s",
*err != nullptr ? *err : "unknown");
}
}
static Local<String> rtc_str(Isolate *isolate, const std::string &s) {
return String::NewFromUtf8(isolate, s.data(), NewStringType::kNormal,
(int)s.size())
.ToLocalChecked();
}
void v8_runt_rtc_init(const FunctionCallbackInfo<Value> &args) {
Isolate *isolate = args.GetIsolate();
g_rtc_isolate = isolate;
args.GetReturnValue().Set(Integer::New(isolate, runt_rtc_init()));
}
void v8_runt_rtc_cleanup(const FunctionCallbackInfo<Value> &args) {
runt_rtc_cleanup();
for (auto *m : {&g_desc_cbs, &g_cand_cbs, &g_state_cbs,
&g_gathering_cbs, &g_dc_cbs, &g_open_cbs,
&g_closed_cbs, &g_error_cbs, &g_msg_cbs}) {
for (auto &kv : *m) kv.second.Reset();
m->clear();
}
g_pc_dcs.clear();
g_dc_to_pc.clear();
RtcEvent ev;
while (runt_rtc_poll_event(ev)) {}
args.GetReturnValue().Set(Undefined(args.GetIsolate()));
}
void v8_runt_rtc_shutdown_all() {
std::set<int> dcs;
for (auto &kv : g_dc_to_pc) dcs.insert(kv.first);
for (auto &kv : g_msg_cbs) dcs.insert(kv.first);
for (auto &kv : g_open_cbs) dcs.insert(kv.first);
for (auto &kv : g_closed_cbs) dcs.insert(kv.first);
for (auto &kv : g_error_cbs) dcs.insert(kv.first);
for (int dc : dcs) runt_rtc_delete_data_channel(dc);
std::set<int> pcs;
for (auto &kv : g_pc_dcs) pcs.insert(kv.first);
for (auto &kv : g_desc_cbs) pcs.insert(kv.first);
for (auto &kv : g_cand_cbs) pcs.insert(kv.first);
for (auto &kv : g_state_cbs) pcs.insert(kv.first);
for (auto &kv : g_gathering_cbs) pcs.insert(kv.first);
for (auto &kv : g_dc_cbs) pcs.insert(kv.first);
for (int pc : pcs) runt_rtc_delete_peer_connection(pc);
for (auto *m : {&g_desc_cbs, &g_cand_cbs, &g_state_cbs,
&g_gathering_cbs, &g_dc_cbs, &g_open_cbs,
&g_closed_cbs, &g_error_cbs, &g_msg_cbs}) {
for (auto &kv : *m) kv.second.Reset();
m->clear();
}
g_pc_dcs.clear();
g_dc_to_pc.clear();
RtcEvent ev;
while (runt_rtc_poll_event(ev)) {}
runt_rtc_cleanup();
}
void v8_runt_rtc_create_peer_connection(const FunctionCallbackInfo<Value> &args) {
Isolate *isolate = args.GetIsolate();
std::vector<std::string> iceServers;
if (args.Length() >= 1 && args[0]->IsArray()) {
Local<Array> arr = args[0].As<Array>();
Local<Context> ctx = isolate->GetCurrentContext();
for (uint32_t i = 0; i < arr->Length(); i++) {
Local<Value> el = arr->Get(ctx, i).ToLocalChecked();
if (el->IsString()) {
String::Utf8Value s(isolate, el);
iceServers.push_back(*s);
}
}
}
int pc = runt_rtc_create_peer_connection(iceServers);
args.GetReturnValue().Set(Integer::New(isolate, pc));
}
void v8_runt_rtc_close_peer_connection(const FunctionCallbackInfo<Value> &args) {
if (args.Length() < 1 || !args[0]->IsNumber()) return;
int pc = args[0]->Int32Value(args.GetIsolate()->GetCurrentContext()).FromJust();
runt_rtc_close_peer_connection(pc);
}
void v8_runt_rtc_delete_peer_connection(const FunctionCallbackInfo<Value> &args) {
if (args.Length() < 1 || !args[0]->IsNumber()) return;
int pc = args[0]->Int32Value(args.GetIsolate()->GetCurrentContext()).FromJust();
runt_rtc_delete_peer_connection(pc);
rtc_drop_cbs(pc);
auto it = g_pc_dcs.find(pc);
if (it != g_pc_dcs.end()) {
for (int dc : it->second) {
rtc_drop_cbs(dc);
g_dc_to_pc.erase(dc);
}
g_pc_dcs.erase(it);
}
}
void v8_runt_rtc_set_local_description(const FunctionCallbackInfo<Value> &args) {
Isolate *isolate = args.GetIsolate();
if (args.Length() < 1 || !args[0]->IsNumber()) return;
int pc = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
const char *type = nullptr;
if (args.Length() >= 2 && args[1]->IsString()) {
String::Utf8Value typeStr(isolate, args[1]);
type = *typeStr;
}
runt_rtc_set_local_description(pc, type);
}
void v8_runt_rtc_set_remote_description(const FunctionCallbackInfo<Value> &args) {
Isolate *isolate = args.GetIsolate();
if (args.Length() < 3 || !args[0]->IsNumber() || !args[1]->IsString() ||
!args[2]->IsString()) {
rtc_throw(isolate,
"runt_rtc_set_remote_description requires (pc, sdp, type)");
return;
}
int pc = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
String::Utf8Value sdp(isolate, args[1]);
String::Utf8Value type(isolate, args[2]);
int ret = runt_rtc_set_remote_description(pc, *sdp, *type);
args.GetReturnValue().Set(Integer::New(isolate, ret));
}
void v8_runt_rtc_add_remote_candidate(const FunctionCallbackInfo<Value> &args) {
Isolate *isolate = args.GetIsolate();
if (args.Length() < 3 || !args[0]->IsNumber() || !args[1]->IsString() ||
!args[2]->IsString()) {
rtc_throw(isolate,
"runt_rtc_add_remote_candidate requires (pc, cand, mid)");
return;
}
int pc = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
String::Utf8Value cand(isolate, args[1]);
String::Utf8Value mid(isolate, args[2]);
int ret = runt_rtc_add_remote_candidate(pc, *cand, *mid);
args.GetReturnValue().Set(Integer::New(isolate, ret));
}
void v8_runt_rtc_get_local_description(const FunctionCallbackInfo<Value> &args) {
Isolate *isolate = args.GetIsolate();
if (args.Length() < 1 || !args[0]->IsNumber()) return;
int pc = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
args.GetReturnValue().Set(
rtc_str(isolate, runt_rtc_get_local_description(pc)));
}
void v8_runt_rtc_get_local_description_type(
const FunctionCallbackInfo<Value> &args) {
Isolate *isolate = args.GetIsolate();
if (args.Length() < 1 || !args[0]->IsNumber()) return;
int pc = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
args.GetReturnValue().Set(
rtc_str(isolate, runt_rtc_get_local_description_type(pc)));
}
void v8_runt_rtc_create_data_channel(const FunctionCallbackInfo<Value> &args) {
Isolate *isolate = args.GetIsolate();
if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsString()) {
rtc_throw(isolate, "runt_rtc_create_data_channel requires (pc, label)");
return;
}
int pc = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
String::Utf8Value label(isolate, args[1]);
bool unordered = false;
int maxRetransmits = -1;
if (args.Length() >= 3 && args[2]->IsBoolean()) {
unordered = args[2]->BooleanValue(isolate);
}
if (args.Length() >= 4 && args[3]->IsNumber()) {
maxRetransmits =
args[3]->Int32Value(isolate->GetCurrentContext()).FromJust();
}
int dc = runt_rtc_create_data_channel(pc, *label, unordered,
maxRetransmits);
if (dc >= 0) rtc_track_dc(pc, dc);
args.GetReturnValue().Set(Integer::New(isolate, dc));
}
void v8_runt_rtc_delete_data_channel(const FunctionCallbackInfo<Value> &args) {
if (args.Length() < 1 || !args[0]->IsNumber()) return;
int dc = args[0]->Int32Value(args.GetIsolate()->GetCurrentContext()).FromJust();
runt_rtc_delete_data_channel(dc);
rtc_drop_cbs(dc);
rtc_forget_dc(dc);
}
void v8_runt_rtc_send_message(const FunctionCallbackInfo<Value> &args) {
Isolate *isolate = args.GetIsolate();
if (args.Length() < 2 || !args[0]->IsNumber()) {
rtc_throw(isolate, "runt_rtc_send_message requires (id, data)");
return;
}
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
int ret = -1;
if (args[1]->IsArrayBuffer()) {
Local<ArrayBuffer> buf = args[1].As<ArrayBuffer>();
ret = runt_rtc_send_message(
id, static_cast<const char *>(buf->GetBackingStore()->Data()),
(int)buf->ByteLength());
} else if (args[1]->IsArrayBufferView()) {
Local<ArrayBufferView> view = args[1].As<ArrayBufferView>();
Local<ArrayBuffer> buf = view->Buffer();
const char *data =
static_cast<const char *>(buf->GetBackingStore()->Data()) +
view->ByteOffset();
ret = runt_rtc_send_message(id, data, (int)view->ByteLength());
} else if (args[1]->IsString()) {
String::Utf8Value s(isolate, args[1]);
ret = runt_rtc_send_message(id, *s, -1);
} else {
rtc_throw(isolate,
"runt_rtc_send_message data must be String or ArrayBuffer");
return;
}
args.GetReturnValue().Set(Integer::New(isolate, ret));
}
void v8_runt_rtc_close(const FunctionCallbackInfo<Value> &args) {
if (args.Length() < 1 || !args[0]->IsNumber()) return;
int id = args[0]->Int32Value(args.GetIsolate()->GetCurrentContext()).FromJust();
runt_rtc_close(id);
}
void v8_runt_rtc_delete(const FunctionCallbackInfo<Value> &args) {
if (args.Length() < 1 || !args[0]->IsNumber()) return;
int id = args[0]->Int32Value(args.GetIsolate()->GetCurrentContext()).FromJust();
runt_rtc_delete(id);
rtc_drop_cbs(id);
rtc_forget_dc(id);
}
void v8_runt_rtc_is_open(const FunctionCallbackInfo<Value> &args) {
Isolate *isolate = args.GetIsolate();
if (args.Length() < 1 || !args[0]->IsNumber()) return;
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
args.GetReturnValue().Set(Boolean::New(isolate, runt_rtc_is_open(id)));
}
void v8_runt_rtc_get_buffered_amount(const FunctionCallbackInfo<Value> &args) {
Isolate *isolate = args.GetIsolate();
if (args.Length() < 1 || !args[0]->IsNumber()) return;
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
args.GetReturnValue().Set(
Integer::New(isolate, runt_rtc_get_buffered_amount(id)));
}
void v8_runt_rtc_set_local_description_callback(
const FunctionCallbackInfo<Value> &args) {
rtc_store_cb(args, g_desc_cbs,
"runt_rtc_set_local_description_callback requires (pc, fn)");
}
void v8_runt_rtc_set_local_candidate_callback(
const FunctionCallbackInfo<Value> &args) {
rtc_store_cb(args, g_cand_cbs,
"runt_rtc_set_local_candidate_callback requires (pc, fn)");
}
void v8_runt_rtc_set_state_change_callback(
const FunctionCallbackInfo<Value> &args) {
rtc_store_cb(args, g_state_cbs,
"runt_rtc_set_state_change_callback requires (pc, fn)");
}
void v8_runt_rtc_set_gathering_state_callback(
const FunctionCallbackInfo<Value> &args) {
rtc_store_cb(args, g_gathering_cbs,
"runt_rtc_set_gathering_state_callback requires (pc, fn)");
}
void v8_runt_rtc_set_data_channel_callback(
const FunctionCallbackInfo<Value> &args) {
rtc_store_cb(args, g_dc_cbs,
"runt_rtc_set_data_channel_callback requires (pc, fn)");
}
void v8_runt_rtc_set_open_callback(const FunctionCallbackInfo<Value> &args) {
rtc_store_cb(args, g_open_cbs,
"runt_rtc_set_open_callback requires (id, fn)");
}
void v8_runt_rtc_set_closed_callback(const FunctionCallbackInfo<Value> &args) {
rtc_store_cb(args, g_closed_cbs,
"runt_rtc_set_closed_callback requires (id, fn)");
}
void v8_runt_rtc_set_error_callback(const FunctionCallbackInfo<Value> &args) {
rtc_store_cb(args, g_error_cbs,
"runt_rtc_set_error_callback requires (id, fn)");
}
void v8_runt_rtc_set_message_callback(const FunctionCallbackInfo<Value> &args) {
rtc_store_cb(args, g_msg_cbs,
"runt_rtc_set_message_callback requires (id, fn)");
}
void v8_runt_rtc_random_bytes(const FunctionCallbackInfo<Value> &args) {
Isolate *isolate = args.GetIsolate();
if (args.Length() < 1 || !args[0]->IsNumber()) {
rtc_throw(isolate, "runt_rtc_random_bytes requires (len)");
return;
}
int len = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
if (len <= 0) {
args.GetReturnValue().Set(Null(isolate));
return;
}
Local<ArrayBuffer> buf = ArrayBuffer::New(isolate, len);
if (!runt_rtc_random_bytes(
static_cast<char *>(buf->GetBackingStore()->Data()), len)) {
args.GetReturnValue().Set(Null(isolate));
return;
}
args.GetReturnValue().Set(buf);
}
static void rtc_drain_events() {
Isolate *isolate = g_rtc_isolate;
RtcEvent ev;
while (runt_rtc_poll_event(ev)) {
switch (ev.type) {
case RtcEventType::LocalDescription: {
auto it = g_desc_cbs.find(ev.id);
if (it != g_desc_cbs.end()) {
Local<Value> argv[2] = {rtc_str(isolate, ev.s1),
rtc_str(isolate, ev.s2)};
rtc_call(it->second, 2, argv);
}
break;
}
case RtcEventType::LocalCandidate: {
auto it = g_cand_cbs.find(ev.id);
if (it != g_cand_cbs.end()) {
Local<Value> argv[2] = {rtc_str(isolate, ev.s1),
rtc_str(isolate, ev.s2)};
rtc_call(it->second, 2, argv);
}
break;
}
case RtcEventType::StateChange: {
auto it = g_state_cbs.find(ev.id);
if (it != g_state_cbs.end()) {
Local<Value> argv[1] = {Integer::New(isolate, ev.arg)};
rtc_call(it->second, 1, argv);
}
break;
}
case RtcEventType::GatheringState: {
auto it = g_gathering_cbs.find(ev.id);
if (it != g_gathering_cbs.end()) {
Local<Value> argv[1] = {Integer::New(isolate, ev.arg)};
rtc_call(it->second, 1, argv);
}
break;
}
case RtcEventType::DataChannel: {
rtc_track_dc(ev.id, ev.arg);
auto it = g_dc_cbs.find(ev.id);
if (it != g_dc_cbs.end()) {
Local<Value> argv[1] = {Integer::New(isolate, ev.arg)};
rtc_call(it->second, 1, argv);
}
break;
}
case RtcEventType::Open: {
auto it = g_open_cbs.find(ev.id);
if (it != g_open_cbs.end()) {
rtc_call(it->second, 0, nullptr);
}
break;
}
case RtcEventType::Closed: {
auto it = g_closed_cbs.find(ev.id);
if (it != g_closed_cbs.end()) {
rtc_call(it->second, 0, nullptr);
}
rtc_drop_cbs(ev.id);
rtc_forget_dc(ev.id);
break;
}
case RtcEventType::Error: {
auto it = g_error_cbs.find(ev.id);
if (it != g_error_cbs.end()) {
Local<Value> argv[1] = {rtc_str(isolate, ev.s1)};
rtc_call(it->second, 1, argv);
}
break;
}
case RtcEventType::Message: {
auto it = g_msg_cbs.find(ev.id);
if (it != g_msg_cbs.end()) {
Local<Value> arg;
if (ev.isString) {
arg = rtc_str(isolate, ev.s1);
} else {
// move the payload into the backing store (no copy)
auto *payload = new std::vector<char>(std::move(ev.data));
auto backing = ArrayBuffer::NewBackingStore(
payload->data(), payload->size(),
[](void *, size_t, void *deleter_data) {
delete static_cast<std::vector<char> *>(deleter_data);
},
payload);
arg = ArrayBuffer::New(isolate, std::move(backing));
}
Local<Value> argv[1] = {arg};
rtc_call(it->second, 1, argv);
}
break;
}
}
}
}
void runt_rtc_process_events_native() {
if (g_rtc_isolate == nullptr || g_rtc_context == nullptr) return;
Locker locker{g_rtc_isolate};
Isolate::Scope isolate_scope(g_rtc_isolate);
HandleScope handle_scope(g_rtc_isolate);
Local<Context> ctx = Local<Context>::New(g_rtc_isolate, *g_rtc_context);
Context::Scope context_scope(ctx);
rtc_drain_events();
}
void bind_rtc_bridge(Isolate *isolate, const Global<Context> &context) {
Locker locker{isolate};
Isolate::Scope isolate_scope(isolate);
HandleScope handle_scope(isolate);
Local<Context> current_context = Local<Context>::New(isolate, context);
Context::Scope context_scope(current_context);
Local<Object> global = current_context->Global();
g_rtc_isolate = isolate;
if (g_rtc_context == nullptr) {
g_rtc_context = new Global<Context>(isolate, context);
}
Local<Object> runt_obj;
Local<Value> existing_runt =
global->Get(current_context,
String::NewFromUtf8(isolate, "RunT").ToLocalChecked())
.ToLocalChecked();
if (existing_runt->IsObject()) {
runt_obj = existing_runt->ToObject(current_context).ToLocalChecked();
} else {
runt_obj = Object::New(isolate);
global->Set(current_context,
String::NewFromUtf8(isolate, "RunT").ToLocalChecked(),
runt_obj);
}
struct RtcBinding {
const char *global_name;
const char *runt_name;
FunctionCallback fn;
};
static const RtcBinding bindings[] = {
{"runt_rtc_init", "rtcInit", v8_runt_rtc_init},
{"runt_rtc_cleanup", "rtcCleanup", v8_runt_rtc_cleanup},
{"runt_rtc_create_peer_connection", "rtcCreatePeerConnection",
v8_runt_rtc_create_peer_connection},
{"runt_rtc_close_peer_connection", "rtcClosePeerConnection",
v8_runt_rtc_close_peer_connection},
{"runt_rtc_delete_peer_connection", "rtcDeletePeerConnection",
v8_runt_rtc_delete_peer_connection},
{"runt_rtc_set_local_description", "rtcSetLocalDescription",
v8_runt_rtc_set_local_description},
{"runt_rtc_set_remote_description", "rtcSetRemoteDescription",
v8_runt_rtc_set_remote_description},
{"runt_rtc_add_remote_candidate", "rtcAddRemoteCandidate",
v8_runt_rtc_add_remote_candidate},
{"runt_rtc_get_local_description", "rtcGetLocalDescription",
v8_runt_rtc_get_local_description},
{"runt_rtc_get_local_description_type", "rtcGetLocalDescriptionType",
v8_runt_rtc_get_local_description_type},
{"runt_rtc_create_data_channel", "rtcCreateDataChannel",
v8_runt_rtc_create_data_channel},
{"runt_rtc_delete_data_channel", "rtcDeleteDataChannel",
v8_runt_rtc_delete_data_channel},
{"runt_rtc_send_message", "rtcSendMessage", v8_runt_rtc_send_message},
{"runt_rtc_close", "rtcClose", v8_runt_rtc_close},
{"runt_rtc_delete", "rtcDelete", v8_runt_rtc_delete},
{"runt_rtc_is_open", "rtcIsOpen", v8_runt_rtc_is_open},
{"runt_rtc_get_buffered_amount", "rtcGetBufferedAmount",
v8_runt_rtc_get_buffered_amount},
{"runt_rtc_set_local_description_callback",
"rtcSetLocalDescriptionCallback",
v8_runt_rtc_set_local_description_callback},
{"runt_rtc_set_local_candidate_callback",
"rtcSetLocalCandidateCallback",
v8_runt_rtc_set_local_candidate_callback},
{"runt_rtc_set_state_change_callback", "rtcSetStateChangeCallback",
v8_runt_rtc_set_state_change_callback},
{"runt_rtc_set_gathering_state_callback", "rtcSetGatheringStateCallback",
v8_runt_rtc_set_gathering_state_callback},
{"runt_rtc_set_data_channel_callback", "rtcSetDataChannelCallback",
v8_runt_rtc_set_data_channel_callback},
{"runt_rtc_set_open_callback", "rtcSetOpenCallback",
v8_runt_rtc_set_open_callback},
{"runt_rtc_set_closed_callback", "rtcSetClosedCallback",
v8_runt_rtc_set_closed_callback},
{"runt_rtc_set_error_callback", "rtcSetErrorCallback",
v8_runt_rtc_set_error_callback},
{"runt_rtc_set_message_callback", "rtcSetMessageCallback",
v8_runt_rtc_set_message_callback},
{"runt_rtc_random_bytes", "rtcRandomBytes",
v8_runt_rtc_random_bytes},
};
for (const RtcBinding &b : bindings) {
Local<Function> fn =
Function::New(current_context, b.fn).ToLocalChecked();
global->Set(current_context,
String::NewFromUtf8(isolate, b.global_name).ToLocalChecked(),
fn);
runt_obj->Set(current_context,
String::NewFromUtf8(isolate, b.runt_name).ToLocalChecked(),
fn);
}
}