realm-js/src/rpc.cpp

393 lines
14 KiB
C++
Raw Normal View History

/* Copyright 2015 Realm Inc - All Rights Reserved
* Proprietary and Confidential
*/
2015-10-08 17:57:07 +00:00
2015-12-01 22:44:09 +00:00
#include "rpc.hpp"
2015-10-08 17:57:07 +00:00
#include <dlfcn.h>
2015-10-08 17:57:07 +00:00
#include <map>
#include <string>
2015-11-30 19:47:32 +00:00
#include "js_init.h"
2015-11-28 02:36:04 +00:00
#include "js_object.hpp"
#include "js_results.hpp"
#include "js_list.hpp"
#include "js_realm.hpp"
#include "js_util.hpp"
2015-10-08 17:57:07 +00:00
#include "base64.hpp"
2015-10-08 17:57:07 +00:00
#include "object_accessor.hpp"
#include "shared_realm.hpp"
#include "results.hpp"
2015-12-02 03:42:47 +00:00
#include <cassert>
using RJSAccessor = realm::NativeAccessor<JSValueRef, JSContextRef>;
2015-10-22 17:44:10 +00:00
using namespace realm_js;
2015-10-08 17:57:07 +00:00
static const char * const RealmObjectTypesData = "data";
static const char * const RealmObjectTypesDate = "date";
static const char * const RealmObjectTypesDictionary = "dict";
static const char * const RealmObjectTypesFunction = "function";
static const char * const RealmObjectTypesList = "list";
static const char * const RealmObjectTypesObject = "object";
static const char * const RealmObjectTypesResults = "results";
static const char * const RealmObjectTypesUndefined = "undefined";
2015-10-22 17:44:10 +00:00
RPCServer::RPCServer() {
2015-10-23 00:59:05 +00:00
m_context = JSGlobalContextCreate(NULL);
2015-10-08 17:57:07 +00:00
2015-10-22 17:44:10 +00:00
// JavaScriptCore crashes when trying to walk up the native stack to print the stacktrace.
// FIXME: Avoid having to do this!
static void (*setIncludesNativeCallStack)(JSGlobalContextRef, bool) = (void (*)(JSGlobalContextRef, bool))dlsym(RTLD_DEFAULT, "JSGlobalContextSetIncludesNativeCallStackWhenReportingExceptions");
if (setIncludesNativeCallStack) {
2015-10-23 00:59:05 +00:00
setIncludesNativeCallStack(m_context, false);
2015-10-16 01:50:20 +00:00
}
2015-10-23 00:59:05 +00:00
m_requests["/create_session"] = [this](const json dict) {
RJSInitializeInContext(m_context);
2015-10-16 01:50:20 +00:00
2015-10-22 17:44:10 +00:00
JSStringRef realm_string = RJSStringForString("Realm");
2015-10-23 00:59:05 +00:00
JSObjectRef realm_constructor = RJSValidatedObjectProperty(m_context, JSContextGetGlobalObject(m_context), realm_string);
2015-10-22 17:44:10 +00:00
JSStringRelease(realm_string);
2015-10-23 00:59:05 +00:00
m_session_id = store_object(realm_constructor);
return (json){{"result", m_session_id}};
2015-10-22 17:44:10 +00:00
};
2015-10-23 00:59:05 +00:00
m_requests["/create_realm"] = [this](const json dict) {
JSObjectRef realm_constructor = m_session_id ? m_objects[m_session_id] : NULL;
2015-10-22 17:44:10 +00:00
if (!realm_constructor) {
throw std::runtime_error("Realm constructor not found!");
}
2015-10-22 17:44:10 +00:00
json::array_t args = dict["arguments"];
size_t arg_count = args.size();
JSValueRef arg_values[arg_count];
2015-10-16 01:50:20 +00:00
2015-10-22 17:44:10 +00:00
for (size_t i = 0; i < arg_count; i++) {
arg_values[i] = deserialize_json_value(args[i]);
}
2015-10-22 17:44:10 +00:00
JSValueRef exception = NULL;
2015-10-23 00:59:05 +00:00
JSObjectRef realm_object = JSObjectCallAsConstructor(m_context, realm_constructor, arg_count, arg_values, &exception);
2015-10-22 17:44:10 +00:00
if (exception) {
2015-10-23 00:59:05 +00:00
return (json){{"error", RJSStringForValue(m_context, exception)}};
2015-10-22 17:44:10 +00:00
}
2015-10-23 00:59:05 +00:00
RPCObjectID realm_id = store_object(realm_object);
return (json){{"result", realm_id}};
2015-10-22 17:44:10 +00:00
};
2015-10-23 00:59:05 +00:00
m_requests["/begin_transaction"] = [this](const json dict) {
RPCObjectID realm_id = dict["realmId"].get<RPCObjectID>();
RJSGetInternal<realm::SharedRealm *>(m_objects[realm_id])->get()->begin_transaction();
2015-10-22 23:49:32 +00:00
return json::object();
2015-10-22 17:44:10 +00:00
};
2015-10-23 00:59:05 +00:00
m_requests["/cancel_transaction"] = [this](const json dict) {
RPCObjectID realm_id = dict["realmId"].get<RPCObjectID>();
RJSGetInternal<realm::SharedRealm *>(m_objects[realm_id])->get()->cancel_transaction();
2015-10-22 23:49:32 +00:00
return json::object();
2015-10-22 17:44:10 +00:00
};
2015-10-23 00:59:05 +00:00
m_requests["/commit_transaction"] = [this](const json dict) {
RPCObjectID realm_id = dict["realmId"].get<RPCObjectID>();
RJSGetInternal<realm::SharedRealm *>(m_objects[realm_id])->get()->commit_transaction();
2015-10-22 23:49:32 +00:00
return json::object();
2015-10-22 17:44:10 +00:00
};
2015-10-23 00:59:05 +00:00
m_requests["/call_method"] = [this](const json dict) {
JSObjectRef object = m_objects[dict["id"].get<RPCObjectID>()];
JSStringRef method_string = RJSStringForString(dict["name"].get<std::string>());
JSObjectRef function = RJSValidatedObjectProperty(m_context, object, method_string);
JSStringRelease(method_string);
2015-10-22 17:44:10 +00:00
json args = dict["arguments"];
2015-10-23 00:59:05 +00:00
size_t count = args.size();
JSValueRef arg_values[count];
for (size_t i = 0; i < count; i++) {
arg_values[i] = deserialize_json_value(args[i]);
2015-10-22 17:44:10 +00:00
}
2015-10-22 17:44:10 +00:00
JSValueRef exception = NULL;
2015-10-23 00:59:05 +00:00
JSValueRef result = JSObjectCallAsFunction(m_context, function, object, count, arg_values, &exception);
2015-10-22 17:44:10 +00:00
if (exception) {
2015-10-23 00:59:05 +00:00
return (json){{"error", RJSStringForValue(m_context, exception)}};
2015-10-22 17:44:10 +00:00
}
2015-10-22 23:49:32 +00:00
return (json){{"result", serialize_json_value(result)}};
2015-10-22 17:44:10 +00:00
};
2015-10-23 00:59:05 +00:00
m_requests["/get_property"] = [this](const json dict) {
2015-10-22 17:44:10 +00:00
RPCObjectID oid = dict["id"].get<RPCObjectID>();
json name = dict["name"];
JSValueRef value = NULL;
JSValueRef exception = NULL;
if (name.is_number()) {
2015-10-23 00:59:05 +00:00
value = JSObjectGetPropertyAtIndex(m_context, m_objects[oid], name.get<unsigned int>(), &exception);
2015-10-22 17:44:10 +00:00
}
else {
2015-10-23 00:59:05 +00:00
JSStringRef prop_string = RJSStringForString(name.get<std::string>());
value = JSObjectGetProperty(m_context, m_objects[oid], prop_string, &exception);
JSStringRelease(prop_string);
2015-10-22 17:44:10 +00:00
}
2015-10-22 17:44:10 +00:00
if (exception) {
2015-10-23 00:59:05 +00:00
return (json){{"error", RJSStringForValue(m_context, exception)}};
2015-10-22 17:44:10 +00:00
}
2015-10-22 23:49:32 +00:00
return (json){{"result", serialize_json_value(value)}};
2015-10-22 17:44:10 +00:00
};
2015-10-23 00:59:05 +00:00
m_requests["/set_property"] = [this](const json dict) {
2015-10-22 17:44:10 +00:00
RPCObjectID oid = dict["id"].get<RPCObjectID>();
json name = dict["name"];
JSValueRef value = deserialize_json_value(dict["value"]);
JSValueRef exception = NULL;
if (name.is_number()) {
2015-10-23 00:59:05 +00:00
JSObjectSetPropertyAtIndex(m_context, m_objects[oid], name.get<unsigned int>(), value, &exception);
2015-10-22 17:44:10 +00:00
}
else {
2015-10-23 01:06:11 +00:00
JSStringRef prop_string = RJSStringForString(name.get<std::string>());
2015-10-23 00:59:05 +00:00
JSObjectSetProperty(m_context, m_objects[oid], prop_string, value, 0, &exception);
2015-10-23 01:06:11 +00:00
JSStringRelease(prop_string);
2015-10-22 17:44:10 +00:00
}
2015-10-22 17:44:10 +00:00
if (exception) {
2015-10-23 00:59:05 +00:00
return (json){{"error", RJSStringForValue(m_context, exception)}};
2015-10-22 17:44:10 +00:00
}
2015-10-22 23:49:32 +00:00
return json::object();
2015-10-22 17:44:10 +00:00
};
2015-10-23 00:59:05 +00:00
m_requests["/dispose_object"] = [this](const json dict) {
2015-10-22 17:44:10 +00:00
RPCObjectID oid = dict["id"].get<RPCObjectID>();
2015-10-23 00:59:05 +00:00
JSValueUnprotect(m_context, m_objects[oid]);
m_objects.erase(oid);
2015-10-22 23:49:32 +00:00
return json::object();
2015-10-22 17:44:10 +00:00
};
2015-10-23 00:59:05 +00:00
m_requests["/clear_test_state"] = [this](const json dict) {
for (auto object : m_objects) {
2015-10-22 17:44:10 +00:00
// The session ID points to the Realm constructor object, which should remain.
2015-10-23 00:59:05 +00:00
if (object.first == m_session_id) {
2015-10-22 17:44:10 +00:00
continue;
2015-10-08 17:57:07 +00:00
}
2015-10-23 00:59:05 +00:00
JSValueUnprotect(m_context, object.second);
m_objects.erase(object.first);
2015-10-22 17:44:10 +00:00
}
2015-10-23 00:59:05 +00:00
JSGarbageCollect(m_context);
RJSClearTestState();
2015-10-22 23:49:32 +00:00
return json::object();
2015-10-22 17:44:10 +00:00
};
}
2015-10-16 00:07:10 +00:00
2015-10-22 17:44:10 +00:00
RPCServer::~RPCServer() {
2015-10-23 00:59:05 +00:00
for (auto item : m_objects) {
JSValueUnprotect(m_context, item.second);
2015-10-08 17:57:07 +00:00
}
2015-10-22 17:44:10 +00:00
2015-10-23 00:59:05 +00:00
JSGlobalContextRelease(m_context);
2015-10-08 17:57:07 +00:00
}
2015-10-22 23:49:32 +00:00
json RPCServer::perform_request(std::string name, json &args) {
2015-10-23 01:06:11 +00:00
try {
RPCRequest action = m_requests[name];
assert(action);
if (name == "/create_session" || m_session_id == args["sessionId"].get<RPCObjectID>()) {
return action(args);
}
else {
return {{"error", "Invalid session ID"}};
2015-10-08 18:23:42 +00:00
}
2015-10-23 01:06:11 +00:00
} catch (std::exception &exception) {
return {{"error", (std::string)"exception thrown: " + exception.what()}};
}
2015-10-08 17:57:07 +00:00
}
2015-10-22 17:44:10 +00:00
RPCObjectID RPCServer::store_object(JSObjectRef object) {
2015-10-08 17:57:07 +00:00
static RPCObjectID s_next_id = 1;
RPCObjectID next_id = s_next_id++;
2015-10-23 00:59:05 +00:00
JSValueProtect(m_context, object);
m_objects[next_id] = object;
2015-10-08 17:57:07 +00:00
return next_id;
}
2015-10-22 17:44:10 +00:00
json RPCServer::serialize_json_value(JSValueRef value) {
2015-10-23 00:59:05 +00:00
switch (JSValueGetType(m_context, value)) {
2015-10-08 17:57:07 +00:00
case kJSTypeUndefined:
2015-10-22 23:49:32 +00:00
return json::object();
2015-10-08 17:57:07 +00:00
case kJSTypeNull:
2015-10-22 23:49:32 +00:00
return {{"value", json(nullptr)}};
2015-10-08 17:57:07 +00:00
case kJSTypeBoolean:
2015-10-23 00:59:05 +00:00
return {{"value", JSValueToBoolean(m_context, value)}};
2015-10-08 17:57:07 +00:00
case kJSTypeNumber:
2015-10-23 00:59:05 +00:00
return {{"value", JSValueToNumber(m_context, value, NULL)}};
2015-10-08 17:57:07 +00:00
case kJSTypeString:
2015-10-23 00:59:05 +00:00
return {{"value", RJSStringForValue(m_context, value)}};
2015-10-08 17:57:07 +00:00
case kJSTypeObject:
break;
}
2015-10-23 00:59:05 +00:00
JSObjectRef js_object = JSValueToObject(m_context, value, NULL);
2015-10-08 17:57:07 +00:00
2015-10-23 00:59:05 +00:00
if (JSValueIsObjectOfClass(m_context, value, RJSObjectClass())) {
realm::Object *object = RJSGetInternal<realm::Object *>(js_object);
2015-10-22 17:44:10 +00:00
return {
{"type", RealmObjectTypesObject},
2015-10-23 00:59:05 +00:00
{"id", store_object(js_object)},
2016-01-05 02:13:09 +00:00
{"schema", serialize_object_schema(object->get_object_schema())}
2015-10-08 17:57:07 +00:00
};
}
2015-10-23 00:59:05 +00:00
else if (JSValueIsObjectOfClass(m_context, value, RJSListClass())) {
realm::List *list = RJSGetInternal<realm::List *>(js_object);
2015-10-22 17:44:10 +00:00
return {
{"type", RealmObjectTypesList},
2015-10-23 00:59:05 +00:00
{"id", store_object(js_object)},
{"size", list->size()},
2016-01-05 02:13:09 +00:00
{"schema", serialize_object_schema(list->get_object_schema())}
2015-10-08 17:57:07 +00:00
};
}
2015-10-23 00:59:05 +00:00
else if (JSValueIsObjectOfClass(m_context, value, RJSResultsClass())) {
realm::Results *results = RJSGetInternal<realm::Results *>(js_object);
2015-10-22 17:44:10 +00:00
return {
2015-10-22 23:49:32 +00:00
{"type", RealmObjectTypesResults},
2015-10-23 00:59:05 +00:00
{"id", store_object(js_object)},
2015-10-22 23:49:32 +00:00
{"size", results->size()},
2016-01-05 02:13:09 +00:00
{"schema", serialize_object_schema(results->get_object_schema())}
2015-10-08 17:57:07 +00:00
};
}
2015-10-23 00:59:05 +00:00
else if (RJSIsValueArray(m_context, value)) {
size_t length = RJSValidatedListLength(m_context, js_object);
2015-10-22 17:44:10 +00:00
std::vector<json> array;
2015-10-08 17:57:07 +00:00
for (unsigned int i = 0; i < length; i++) {
2015-10-23 00:59:05 +00:00
array.push_back(serialize_json_value(JSObjectGetPropertyAtIndex(m_context, js_object, i, NULL)));
2015-10-08 17:57:07 +00:00
}
2015-10-22 23:49:32 +00:00
return {{"value", array}};
2015-10-08 17:57:07 +00:00
}
else if (RJSIsValueArrayBuffer(m_context, value)) {
std::string data = RJSAccessor::to_binary(m_context, value);
return {
{"type", RealmObjectTypesData},
{"value", base64_encode((unsigned char *)data.data(), data.size())},
};
}
2015-10-23 00:59:05 +00:00
else if (RJSIsValueDate(m_context, value)) {
2015-10-22 17:44:10 +00:00
return {
{"type", RealmObjectTypesDate},
2015-10-23 00:59:05 +00:00
{"value", RJSValidatedValueToNumber(m_context, value)},
2015-10-19 23:46:56 +00:00
};
}
else {
// Serialize this JS object as a plain object since it doesn't match any known types above.
JSPropertyNameArrayRef js_keys = JSObjectCopyPropertyNames(m_context, js_object);
size_t count = JSPropertyNameArrayGetCount(js_keys);
std::vector<std::string> keys;
std::vector<json> values;
for (size_t i = 0; i < count; i++) {
JSStringRef js_key = JSPropertyNameArrayGetNameAtIndex(js_keys, i);
JSValueRef js_value = RJSValidatedPropertyValue(m_context, js_object, js_key);
keys.push_back(RJSStringForJSString(js_key));
values.push_back(serialize_json_value(js_value));
}
JSPropertyNameArrayRelease(js_keys);
return {
{"type", RealmObjectTypesDictionary},
{"keys", keys},
{"values", values},
};
}
2015-10-22 17:44:10 +00:00
assert(0);
2015-10-08 17:57:07 +00:00
}
2015-10-26 22:27:43 +00:00
json RPCServer::serialize_object_schema(const realm::ObjectSchema &object_schema) {
std::vector<std::string> properties;
2015-10-23 00:59:05 +00:00
for (realm::Property prop : object_schema.properties) {
properties.push_back(prop.name);
2015-10-08 17:57:07 +00:00
}
2015-10-22 17:44:10 +00:00
return {
2015-10-23 00:59:05 +00:00
{"name", object_schema.name},
2015-10-22 23:49:32 +00:00
{"properties", properties},
2015-10-08 17:57:07 +00:00
};
}
2015-10-22 17:44:10 +00:00
JSValueRef RPCServer::deserialize_json_value(const json dict)
{
2015-10-22 23:49:32 +00:00
json oid = dict["id"];
if (oid.is_number()) {
2015-10-23 00:59:05 +00:00
return m_objects[oid.get<RPCObjectID>()];
2015-10-08 17:57:07 +00:00
}
2015-10-22 22:31:26 +00:00
json value = dict["value"];
2015-10-22 23:49:32 +00:00
json type = dict["type"];
if (type.is_string()) {
std::string type_string = type.get<std::string>();
if (type_string == RealmObjectTypesFunction) {
// FIXME: Make this actually call the function by its id once we need it to.
2015-10-23 00:59:05 +00:00
JSStringRef js_body = JSStringCreateWithUTF8CString("");
JSObjectRef js_function = JSObjectMakeFunction(m_context, NULL, 0, NULL, js_body, NULL, 1, NULL);
JSStringRelease(js_body);
2015-10-22 23:49:32 +00:00
2015-10-23 00:59:05 +00:00
return js_function;
2015-10-22 23:49:32 +00:00
}
else if (type_string == RealmObjectTypesDictionary) {
JSObjectRef js_object = JSObjectMake(m_context, NULL, NULL);
json keys = dict["keys"];
json values = dict["values"];
size_t count = keys.size();
for (size_t i = 0; i < count; i++) {
JSStringRef js_key = RJSStringForString(keys.at(i));
JSValueRef js_value = deserialize_json_value(values.at(i));
JSObjectSetProperty(m_context, js_object, js_key, js_value, 0, NULL);
JSStringRelease(js_key);
}
return js_object;
}
else if (type_string == RealmObjectTypesData) {
std::string bytes;
if (!base64_decode(value.get<std::string>(), &bytes)) {
throw std::runtime_error("Failed to decode base64 encoded data");
}
return RJSAccessor::from_binary(m_context, realm::BinaryData(bytes));
}
else if (type_string == RealmObjectTypesDate) {
2015-10-22 23:49:32 +00:00
JSValueRef exception = NULL;
2015-10-23 00:59:05 +00:00
JSValueRef time = JSValueMakeNumber(m_context, value.get<double>());
JSObjectRef date = JSObjectMakeDate(m_context, 1, &time, &exception);
2015-10-19 23:46:56 +00:00
2015-10-22 23:49:32 +00:00
if (exception) {
2015-10-23 00:59:05 +00:00
throw RJSException(m_context, exception);
2015-10-22 23:49:32 +00:00
}
return date;
2015-10-19 23:46:56 +00:00
}
else if (type_string == RealmObjectTypesUndefined) {
return JSValueMakeUndefined(m_context);
}
assert(0);
2015-10-19 23:46:56 +00:00
}
2015-10-22 23:49:32 +00:00
if (value.is_null()) {
2015-10-23 00:59:05 +00:00
return JSValueMakeNull(m_context);
2015-10-08 17:57:07 +00:00
}
2015-10-22 22:31:26 +00:00
else if (value.is_boolean()) {
2015-10-23 00:59:05 +00:00
return JSValueMakeBoolean(m_context, value.get<bool>());
2015-10-08 17:57:07 +00:00
}
2015-10-22 22:31:26 +00:00
else if (value.is_number()) {
2015-10-23 00:59:05 +00:00
return JSValueMakeNumber(m_context, value.get<double>());
2015-10-08 17:57:07 +00:00
}
2015-10-22 22:31:26 +00:00
else if (value.is_string()) {
2015-10-23 00:59:05 +00:00
return RJSValueForString(m_context, value.get<std::string>());
2015-10-08 17:57:07 +00:00
}
2015-10-22 22:31:26 +00:00
else if (value.is_array()) {
size_t count = value.size();
2015-10-23 00:59:05 +00:00
JSValueRef js_values[count];
2015-10-08 17:57:07 +00:00
2015-10-22 22:31:26 +00:00
for (size_t i = 0; i < count; i++) {
2015-10-23 00:59:05 +00:00
js_values[i] = deserialize_json_value(value.at(i));
2015-10-08 17:57:07 +00:00
}
2015-10-23 00:59:05 +00:00
return JSObjectMakeArray(m_context, count, js_values, NULL);
2015-10-08 17:57:07 +00:00
}
assert(0);
2015-10-08 17:57:07 +00:00
}