661 lines
16 KiB
C++
661 lines
16 KiB
C++
/*
|
|
* mod_v8 for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
|
* Copyright (C) 2013-2014, Peter Olsson <peter@olssononline.se>
|
|
*
|
|
* Version: MPL 1.1
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
* http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the
|
|
* License.
|
|
*
|
|
* The Original Code is mod_v8 for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
|
*
|
|
* The Initial Developer of the Original Code is
|
|
* Peter Olsson <peter@olssononline.se>
|
|
* Portions created by the Initial Developer are Copyright (C)
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Peter Olsson <peter@olssononline.se>
|
|
*
|
|
* fseventhandler.cpp -- JavaScript EventHandler class
|
|
*
|
|
*/
|
|
|
|
#include "fseventhandler.hpp"
|
|
#include "fsevent.hpp"
|
|
#include "fssession.hpp"
|
|
|
|
#define MAX_QUEUE_LEN 100000
|
|
|
|
using namespace std;
|
|
using namespace v8;
|
|
|
|
typedef struct {
|
|
char *cmd;
|
|
char *arg;
|
|
char uuid_str[SWITCH_UUID_FORMATTED_LENGTH + 1];
|
|
int ack;
|
|
switch_memory_pool_t *pool;
|
|
} api_command_struct_t;
|
|
|
|
static const char js_class_name[] = "EventHandler";
|
|
|
|
FSEventHandler::~FSEventHandler(void)
|
|
{
|
|
v8_remove_event_handler(this);
|
|
|
|
if (_event_hash) switch_core_hash_destroy(&_event_hash);
|
|
|
|
if (_event_queue) {
|
|
void *pop;
|
|
|
|
while (switch_queue_trypop(_event_queue, &pop) == SWITCH_STATUS_SUCCESS) {
|
|
switch_event_t *pevent = (switch_event_t *) pop;
|
|
if (pevent) {
|
|
switch_event_destroy(&pevent);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_filters) switch_event_destroy(&_filters);
|
|
if (_mutex) switch_mutex_destroy(_mutex);
|
|
if (_pool) switch_core_destroy_memory_pool(&_pool);
|
|
}
|
|
|
|
void FSEventHandler::Init()
|
|
{
|
|
if (switch_core_new_memory_pool(&_pool) != SWITCH_STATUS_SUCCESS) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "OH OH no pool\n");
|
|
return;
|
|
}
|
|
|
|
switch_mutex_init(&_mutex, SWITCH_MUTEX_NESTED, _pool);
|
|
switch_core_hash_init(&_event_hash, _pool);
|
|
switch_queue_create(&_event_queue, MAX_QUEUE_LEN, _pool);
|
|
|
|
_filters = NULL;
|
|
memset(&_event_list, 0, sizeof(_event_list));
|
|
|
|
v8_add_event_handler(this);
|
|
}
|
|
|
|
string FSEventHandler::GetJSClassName()
|
|
{
|
|
return js_class_name;
|
|
}
|
|
|
|
void FSEventHandler::QueueEvent(switch_event_t *event)
|
|
{
|
|
switch_event_t *clone;
|
|
int send = 0;
|
|
|
|
switch_mutex_lock(_mutex);
|
|
|
|
if (_event_list[SWITCH_EVENT_ALL]) {
|
|
send = 1;
|
|
} else if ((_event_list[event->event_id])) {
|
|
if (event->event_id != SWITCH_EVENT_CUSTOM || !event->subclass_name || (switch_core_hash_find(_event_hash, event->subclass_name))) {
|
|
send = 1;
|
|
}
|
|
}
|
|
|
|
if (send) {
|
|
if (_filters && _filters->headers) {
|
|
switch_event_header_t *hp;
|
|
const char *hval;
|
|
|
|
send = 0;
|
|
|
|
for (hp = _filters->headers; hp; hp = hp->next) {
|
|
if ((hval = switch_event_get_header(event, hp->name))) {
|
|
const char *comp_to = hp->value;
|
|
int pos = 1, cmp = 0;
|
|
|
|
while (comp_to && *comp_to) {
|
|
if (*comp_to == '+') {
|
|
pos = 1;
|
|
} else if (*comp_to == '-') {
|
|
pos = 0;
|
|
} else if (*comp_to != ' ') {
|
|
break;
|
|
}
|
|
comp_to++;
|
|
}
|
|
|
|
if (send && pos) {
|
|
continue;
|
|
}
|
|
|
|
if (!comp_to) {
|
|
continue;
|
|
}
|
|
|
|
if (*hp->value == '/') {
|
|
switch_regex_t *re = NULL;
|
|
int ovector[30];
|
|
cmp = !!switch_regex_perform(hval, comp_to, &re, ovector, sizeof(ovector) / sizeof(ovector[0]));
|
|
switch_regex_safe_free(re);
|
|
} else {
|
|
cmp = !strcasecmp(hval, comp_to);
|
|
}
|
|
|
|
if (cmp) {
|
|
if (pos) {
|
|
send = 1;
|
|
} else {
|
|
send = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
switch_mutex_unlock(_mutex);
|
|
|
|
if (send) {
|
|
if (switch_event_dup(&clone, event) == SWITCH_STATUS_SUCCESS) {
|
|
|
|
if (switch_queue_trypush(_event_queue, clone) == SWITCH_STATUS_SUCCESS) {
|
|
// TODO
|
|
/*if (l->lost_events) {
|
|
int le = l->lost_events;
|
|
l->lost_events = 0;
|
|
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(l->session), SWITCH_LOG_CRIT, "Lost %d events!\n", le);
|
|
}*/
|
|
} else {
|
|
/*if (++l->lost_events > MAX_MISSED) {
|
|
kill_listener(l, NULL);
|
|
}*/
|
|
switch_event_destroy(&clone);
|
|
}
|
|
} else {
|
|
////switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(l->session), SWITCH_LOG_ERROR, "Memory Error!\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
static char *MARKER = "1";
|
|
|
|
void FSEventHandler::DoSubscribe(const v8::FunctionCallbackInfo<v8::Value>& info)
|
|
{
|
|
int i, custom = 0;
|
|
bool ret = false;
|
|
|
|
for (i = 0; i < info.Length(); i++) {
|
|
String::Utf8Value str(info[i]);
|
|
switch_event_types_t etype;
|
|
|
|
if (custom) {
|
|
switch_mutex_lock(_mutex);
|
|
switch_core_hash_insert(_event_hash, js_safe_str(*str), MARKER);
|
|
switch_mutex_unlock(_mutex);
|
|
} else if (switch_name_event(js_safe_str(*str), &etype) == SWITCH_STATUS_SUCCESS) {
|
|
ret = true;
|
|
|
|
if (etype == SWITCH_EVENT_ALL) {
|
|
uint32_t x = 0;
|
|
for (x = 0; x < SWITCH_EVENT_ALL; x++) {
|
|
_event_list[x] = 1;
|
|
}
|
|
}
|
|
|
|
if (etype <= SWITCH_EVENT_ALL) {
|
|
_event_list[etype] = 1;
|
|
}
|
|
|
|
if (etype == SWITCH_EVENT_CUSTOM) {
|
|
custom++;
|
|
}
|
|
}
|
|
}
|
|
|
|
info.GetReturnValue().Set(ret);
|
|
}
|
|
|
|
void *FSEventHandler::Construct(const v8::FunctionCallbackInfo<v8::Value>& info)
|
|
{
|
|
FSEventHandler *obj = new FSEventHandler(info);
|
|
obj->DoSubscribe(info);
|
|
return obj;
|
|
}
|
|
|
|
JS_EVENTHANDLER_FUNCTION_IMPL(Subscribe)
|
|
{
|
|
DoSubscribe(info);
|
|
}
|
|
|
|
JS_EVENTHANDLER_FUNCTION_IMPL(UnSubscribe)
|
|
{
|
|
int i, custom = 0;
|
|
bool ret = false;
|
|
|
|
for (i = 0; i < info.Length(); i++) {
|
|
String::Utf8Value str(info[i]);
|
|
switch_event_types_t etype;
|
|
|
|
if (custom) {
|
|
switch_mutex_lock(_mutex);
|
|
switch_core_hash_delete(_event_hash, js_safe_str(*str));
|
|
switch_mutex_unlock(_mutex);
|
|
} else if (switch_name_event(js_safe_str(*str), &etype) == SWITCH_STATUS_SUCCESS) {
|
|
uint32_t x = 0;
|
|
ret = true;
|
|
|
|
if (etype == SWITCH_EVENT_CUSTOM) {
|
|
custom++;
|
|
} else if (etype == SWITCH_EVENT_ALL) {
|
|
for (x = 0; x <= SWITCH_EVENT_ALL; x++) {
|
|
_event_list[x] = 0;
|
|
}
|
|
} else {
|
|
if (_event_list[SWITCH_EVENT_ALL]) {
|
|
_event_list[SWITCH_EVENT_ALL] = 0;
|
|
for (x = 0; x < SWITCH_EVENT_ALL; x++) {
|
|
_event_list[x] = 1;
|
|
}
|
|
}
|
|
_event_list[etype] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
info.GetReturnValue().Set(ret);
|
|
}
|
|
|
|
JS_EVENTHANDLER_FUNCTION_IMPL(DeleteFilter)
|
|
{
|
|
if (info.Length() < 1) {
|
|
info.GetReturnValue().Set(false);
|
|
} else {
|
|
String::Utf8Value str(info[0]);
|
|
const char *headerName = js_safe_str(*str);
|
|
|
|
if (zstr(headerName)) {
|
|
info.GetReturnValue().Set(false);
|
|
return;
|
|
}
|
|
|
|
switch_mutex_lock(_mutex);
|
|
|
|
if (!_filters) {
|
|
switch_event_create_plain(&_filters, SWITCH_EVENT_CLONE);
|
|
}
|
|
|
|
if (!strcasecmp(headerName, "all")) {
|
|
switch_event_destroy(&_filters);
|
|
switch_event_create_plain(&_filters, SWITCH_EVENT_CLONE);
|
|
} else {
|
|
switch_event_del_header(_filters, headerName);
|
|
}
|
|
|
|
info.GetReturnValue().Set(true);
|
|
|
|
switch_mutex_unlock(_mutex);
|
|
}
|
|
}
|
|
|
|
JS_EVENTHANDLER_FUNCTION_IMPL(AddFilter)
|
|
{
|
|
if (info.Length() < 2) {
|
|
info.GetReturnValue().Set(false);
|
|
} else {
|
|
String::Utf8Value str1(info[0]);
|
|
String::Utf8Value str2(info[1]);
|
|
const char *headerName = js_safe_str(*str1);
|
|
const char *headerVal = js_safe_str(*str2);
|
|
|
|
if (zstr(headerName) || zstr(headerVal)) {
|
|
info.GetReturnValue().Set(false);
|
|
return;
|
|
}
|
|
|
|
switch_mutex_lock(_mutex);
|
|
|
|
if (!_filters) {
|
|
switch_event_create_plain(&_filters, SWITCH_EVENT_CLONE);
|
|
}
|
|
|
|
switch_event_add_header_string(_filters, SWITCH_STACK_BOTTOM, headerName, headerVal);
|
|
|
|
info.GetReturnValue().Set(true);
|
|
|
|
switch_mutex_unlock(_mutex);
|
|
}
|
|
}
|
|
|
|
JS_EVENTHANDLER_FUNCTION_IMPL(GetEvent)
|
|
{
|
|
void *pop = NULL;
|
|
int timeout = 0;
|
|
switch_event_t *pevent = NULL;
|
|
|
|
if (info.Length() > 0 && !info[0].IsEmpty()) {
|
|
timeout = info[0]->Int32Value();
|
|
}
|
|
|
|
if (timeout > 0) {
|
|
if (switch_queue_pop_timeout(_event_queue, &pop, (switch_interval_time_t) timeout * 1000) == SWITCH_STATUS_SUCCESS && pop) {
|
|
pevent = (switch_event_t *) pop;
|
|
}
|
|
} else {
|
|
if (switch_queue_trypop(_event_queue, &pop) == SWITCH_STATUS_SUCCESS && pop) {
|
|
pevent = (switch_event_t *) pop;
|
|
}
|
|
}
|
|
|
|
if (pevent) {
|
|
FSEvent *evt = new FSEvent(info);
|
|
evt->SetEvent(pevent, 0);
|
|
evt->RegisterInstance(info.GetIsolate(), "", true);
|
|
info.GetReturnValue().Set(evt->GetJavaScriptObject());
|
|
} else {
|
|
info.GetReturnValue().Set(Null(info.GetIsolate()));
|
|
}
|
|
}
|
|
|
|
JS_EVENTHANDLER_FUNCTION_IMPL(SendEvent)
|
|
{
|
|
if (info.Length() == 0) {
|
|
info.GetReturnValue().Set(false);
|
|
} else {
|
|
if (!info[0].IsEmpty() && info[0]->IsObject()) {
|
|
FSEvent *evt = JSBase::GetInstance<FSEvent>(info[0]->ToObject());
|
|
switch_event_t **event;
|
|
|
|
if (!evt || !(event = evt->GetEvent())) {
|
|
info.GetReturnValue().Set(false);
|
|
} else {
|
|
string session_uuid;
|
|
|
|
if (info.Length() > 1) {
|
|
if (!info[1].IsEmpty() && info[1]->IsObject()) {
|
|
/* The second argument is a session object */
|
|
FSSession *sess = JSBase::GetInstance<FSSession>(info[1]->ToObject());
|
|
switch_core_session_t *tmp;
|
|
|
|
if (sess && (tmp = sess->GetSession())) {
|
|
session_uuid = switch_core_session_get_uuid(tmp);
|
|
}
|
|
} else {
|
|
/* The second argument is a session uuid string */
|
|
String::Utf8Value str(info[1]);
|
|
session_uuid = js_safe_str(*str);
|
|
}
|
|
}
|
|
|
|
if (session_uuid.length() > 0) {
|
|
/* This is a session event */
|
|
switch_core_session_t *session;
|
|
switch_status_t status = SWITCH_STATUS_FALSE;
|
|
|
|
if ((session = switch_core_session_locate(session_uuid.c_str()))) {
|
|
if ((status = switch_core_session_queue_private_event(session, event, SWITCH_FALSE)) == SWITCH_STATUS_SUCCESS) {
|
|
info.GetReturnValue().Set(true);
|
|
} else {
|
|
info.GetReturnValue().Set(false);
|
|
}
|
|
switch_core_session_rwunlock(session);
|
|
} else {
|
|
info.GetReturnValue().Set(false);
|
|
// TODO LOGGING
|
|
//switch_snprintf(reply, reply_len, "-ERR invalid session id [%s]", switch_str_nil(uuid));
|
|
}
|
|
} else {
|
|
/* "Normal" event */
|
|
// TODO LOGGING
|
|
switch_event_fire(event);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
JS_EVENTHANDLER_FUNCTION_IMPL(ExecuteApi)
|
|
{
|
|
if (info.Length() > 0) {
|
|
String::Utf8Value str(info[0]);
|
|
const char *cmd = js_safe_str(*str);
|
|
string arg;
|
|
switch_stream_handle_t stream = { 0 };
|
|
|
|
if (!strcasecmp(cmd, "jsapi")) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Possible recursive API Call is not allowed\n");
|
|
info.GetReturnValue().Set(false);
|
|
return;
|
|
}
|
|
|
|
if (info.Length() > 1) {
|
|
String::Utf8Value str2(info[1]);
|
|
arg = js_safe_str(*str2);
|
|
}
|
|
|
|
SWITCH_STANDARD_STREAM(stream);
|
|
switch_api_execute(cmd, arg.c_str(), NULL, &stream);
|
|
|
|
info.GetReturnValue().Set(String::NewFromUtf8(info.GetIsolate(), switch_str_nil((char *) stream.data)));
|
|
switch_safe_free(stream.data);
|
|
} else {
|
|
info.GetReturnValue().Set(String::NewFromUtf8(info.GetIsolate(), "-ERR"));
|
|
}
|
|
}
|
|
|
|
static void *SWITCH_THREAD_FUNC api_exec(switch_thread_t *thread, void *obj)
|
|
{
|
|
api_command_struct_t *acs = (api_command_struct_t *) obj;
|
|
switch_stream_handle_t stream = { 0 };
|
|
char *reply, *freply = NULL;
|
|
switch_status_t status;
|
|
switch_event_t *event;
|
|
|
|
if (!acs) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Internal error.\n");
|
|
return NULL;
|
|
}
|
|
|
|
acs->ack = 1;
|
|
|
|
SWITCH_STANDARD_STREAM(stream);
|
|
|
|
status = switch_api_execute(acs->cmd, acs->arg, NULL, &stream);
|
|
|
|
if (status == SWITCH_STATUS_SUCCESS) {
|
|
reply = (char *)stream.data;
|
|
} else {
|
|
freply = switch_mprintf("-ERR %s Command not found!\n", acs->cmd);
|
|
reply = freply;
|
|
}
|
|
|
|
if (!reply) {
|
|
reply = "Command returned no output!";
|
|
}
|
|
|
|
if (switch_event_create(&event, SWITCH_EVENT_BACKGROUND_JOB) == SWITCH_STATUS_SUCCESS) {
|
|
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Job-UUID", acs->uuid_str);
|
|
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Job-Command", acs->cmd);
|
|
if (acs->arg) {
|
|
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Job-Command-Arg", acs->arg);
|
|
}
|
|
switch_event_add_body(event, "%s", reply);
|
|
switch_event_fire(&event);
|
|
}
|
|
|
|
switch_safe_free(stream.data);
|
|
switch_safe_free(freply);
|
|
|
|
switch_memory_pool_t *pool = acs->pool;
|
|
if (acs->ack == -1) {
|
|
int sanity = 2000;
|
|
while (acs->ack == -1) {
|
|
switch_cond_next();
|
|
if (--sanity <= 0)
|
|
break;
|
|
}
|
|
}
|
|
|
|
acs = NULL;
|
|
switch_core_destroy_memory_pool(&pool);
|
|
pool = NULL;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
JS_EVENTHANDLER_FUNCTION_IMPL(ExecuteBgApi)
|
|
{
|
|
string cmd;
|
|
string arg;
|
|
string jobuuid;
|
|
api_command_struct_t *acs = NULL;
|
|
switch_memory_pool_t *pool;
|
|
switch_thread_t *thread;
|
|
switch_threadattr_t *thd_attr = NULL;
|
|
switch_uuid_t uuid;
|
|
int sanity = 2000;
|
|
|
|
if (info.Length() > 0) {
|
|
String::Utf8Value str(info[0]);
|
|
cmd = js_safe_str(*str);
|
|
|
|
if (info.Length() > 1) {
|
|
String::Utf8Value str2(info[1]);
|
|
arg = js_safe_str(*str2);
|
|
}
|
|
|
|
if (info.Length() > 2) {
|
|
String::Utf8Value str2(info[2]);
|
|
jobuuid = js_safe_str(*str2);
|
|
}
|
|
} else {
|
|
info.GetReturnValue().Set(false);
|
|
return;
|
|
}
|
|
|
|
if (cmd.length() == 0) {
|
|
info.GetReturnValue().Set(false);
|
|
return;
|
|
}
|
|
|
|
switch_core_new_memory_pool(&pool);
|
|
acs = (api_command_struct_t *)switch_core_alloc(pool, sizeof(*acs));
|
|
switch_assert(acs);
|
|
acs->pool = pool;
|
|
|
|
acs->cmd = switch_core_strdup(acs->pool, cmd.c_str());
|
|
|
|
if (arg.c_str()) {
|
|
acs->arg = switch_core_strdup(acs->pool, arg.c_str());
|
|
}
|
|
|
|
switch_threadattr_create(&thd_attr, acs->pool);
|
|
switch_threadattr_detach_set(thd_attr, 1);
|
|
switch_threadattr_stacksize_set(thd_attr, SWITCH_THREAD_STACKSIZE);
|
|
|
|
if (jobuuid.length() > 0) {
|
|
switch_copy_string(acs->uuid_str, jobuuid.c_str(), sizeof(acs->uuid_str));
|
|
} else {
|
|
switch_uuid_get(&uuid);
|
|
switch_uuid_format(acs->uuid_str, &uuid);
|
|
}
|
|
|
|
info.GetReturnValue().Set(String::NewFromUtf8(info.GetIsolate(), acs->uuid_str));
|
|
|
|
switch_thread_create(&thread, thd_attr, api_exec, acs, acs->pool);
|
|
|
|
while (!acs->ack) {
|
|
switch_cond_next();
|
|
if (--sanity <= 0)
|
|
break;
|
|
}
|
|
|
|
if (acs->ack == -1) {
|
|
acs->ack--;
|
|
}
|
|
}
|
|
|
|
JS_EVENTHANDLER_FUNCTION_IMPL_STATIC(Destroy)
|
|
{
|
|
JS_CHECK_SCRIPT_STATE();
|
|
|
|
FSEventHandler *obj = JSBase::GetInstance<FSEventHandler>(info.Holder());
|
|
|
|
if (obj) {
|
|
delete obj;
|
|
info.GetReturnValue().Set(true);
|
|
} else {
|
|
info.GetReturnValue().Set(false);
|
|
}
|
|
}
|
|
|
|
JS_EVENTHANDLER_GET_PROPERTY_IMPL_STATIC(GetReadyProperty)
|
|
{
|
|
JS_CHECK_SCRIPT_STATE();
|
|
|
|
FSEventHandler *obj = JSBase::GetInstance<FSEventHandler>(info.Holder());
|
|
|
|
if (obj) {
|
|
info.GetReturnValue().Set(true);
|
|
} else {
|
|
info.GetReturnValue().Set(false);
|
|
}
|
|
}
|
|
|
|
static const js_function_t eventhandler_methods[] = {
|
|
{"subscribe", FSEventHandler::Subscribe},
|
|
{"unSubscribe", FSEventHandler::UnSubscribe},
|
|
{"addFilter", FSEventHandler::AddFilter},
|
|
{"deleteFilter", FSEventHandler::DeleteFilter},
|
|
{"getEvent", FSEventHandler::GetEvent},
|
|
{"sendEvent", FSEventHandler::SendEvent},
|
|
{"executeApi", FSEventHandler::ExecuteApi},
|
|
{"executeBgApi", FSEventHandler::ExecuteBgApi},
|
|
{"destroy", FSEventHandler::Destroy},
|
|
{0}
|
|
};
|
|
|
|
static const js_property_t eventhandler_props[] = {
|
|
{"ready", FSEventHandler::GetReadyProperty, JSBase::DefaultSetProperty},
|
|
{0}
|
|
};
|
|
|
|
static const js_class_definition_t eventhandler_desc = {
|
|
js_class_name,
|
|
FSEventHandler::Construct,
|
|
eventhandler_methods,
|
|
eventhandler_props
|
|
};
|
|
|
|
static switch_status_t eventhandler_load(const v8::FunctionCallbackInfo<Value>& info)
|
|
{
|
|
JSBase::Register(info.GetIsolate(), &eventhandler_desc);
|
|
return SWITCH_STATUS_SUCCESS;
|
|
}
|
|
|
|
static const v8_mod_interface_t eventhandler_module_interface = {
|
|
/*.name = */ js_class_name,
|
|
/*.js_mod_load */ eventhandler_load
|
|
};
|
|
|
|
const v8_mod_interface_t *FSEventHandler::GetModuleInterface()
|
|
{
|
|
return &eventhandler_module_interface;
|
|
}
|
|
|
|
/* For Emacs:
|
|
* Local Variables:
|
|
* mode:c
|
|
* indent-tabs-mode:t
|
|
* tab-width:4
|
|
* c-basic-offset:4
|
|
* End:
|
|
* For VIM:
|
|
* vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet:
|
|
*/
|