2015-10-08 17:57:07 +00:00
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// Copyright 2015 Realm Inc.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
//
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
2015-10-22 17:44:10 +00:00
|
|
|
#import "RealmRPC.hpp"
|
2015-10-08 17:57:07 +00:00
|
|
|
|
2015-10-19 22:26:42 +00:00
|
|
|
#include <dlfcn.h>
|
2015-10-08 17:57:07 +00:00
|
|
|
#include <map>
|
|
|
|
#include <string>
|
|
|
|
#include "RealmJS.h"
|
|
|
|
#include "RJSObject.hpp"
|
|
|
|
#include "RJSResults.hpp"
|
2015-10-13 22:27:24 +00:00
|
|
|
#include "RJSList.hpp"
|
2015-10-08 17:57:07 +00:00
|
|
|
#include "RJSRealm.hpp"
|
|
|
|
#include "RJSUtil.hpp"
|
|
|
|
|
|
|
|
#include "object_accessor.hpp"
|
|
|
|
#include "shared_realm.hpp"
|
|
|
|
#include "results.hpp"
|
|
|
|
|
2015-10-22 17:44:10 +00:00
|
|
|
using namespace realm_js;
|
2015-10-08 17:57:07 +00:00
|
|
|
|
2015-10-19 19:06:47 +00:00
|
|
|
static const char * const RealmObjectTypesFunction = "ObjectTypesFUNCTION";
|
|
|
|
static const char * const RealmObjectTypesNotification = "ObjectTypesNOTIFICATION";
|
|
|
|
static const char * const RealmObjectTypesResults = "ObjectTypesRESULTS";
|
|
|
|
|
2015-10-22 17:44:10 +00:00
|
|
|
RPCServer::RPCServer() {
|
|
|
|
_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) {
|
|
|
|
setIncludesNativeCallStack(_context, false);
|
2015-10-16 01:50:20 +00:00
|
|
|
}
|
|
|
|
|
2015-10-22 17:44:10 +00:00
|
|
|
_requests["/create_session"] = [=](const json dict) {
|
|
|
|
[RealmJS initializeContext:_context];
|
2015-10-16 01:50:20 +00:00
|
|
|
|
2015-10-22 17:44:10 +00:00
|
|
|
JSStringRef realm_string = RJSStringForString("Realm");
|
|
|
|
JSObjectRef realm_constructor = RJSValidatedObjectProperty(_context, JSContextGetGlobalObject(_context), realm_string);
|
|
|
|
JSStringRelease(realm_string);
|
2015-10-15 10:00:13 +00:00
|
|
|
|
2015-10-22 17:44:10 +00:00
|
|
|
_sessionID = store_object(realm_constructor);
|
|
|
|
return json({"result", _sessionID});
|
|
|
|
};
|
|
|
|
_requests["/create_realm"] = [=](const json dict) {
|
|
|
|
JSObjectRef realm_constructor = _sessionID ? _objects[_sessionID] : NULL;
|
|
|
|
if (!realm_constructor) {
|
|
|
|
throw std::runtime_error("Realm constructor not found!");
|
2015-10-19 22:26:42 +00:00
|
|
|
}
|
|
|
|
|
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-20 22:10:22 +00:00
|
|
|
|
2015-10-22 17:44:10 +00:00
|
|
|
JSValueRef exception = NULL;
|
|
|
|
JSObjectRef realmObject = JSObjectCallAsConstructor(_context, realm_constructor, arg_count, arg_values, &exception);
|
2015-10-20 22:10:22 +00:00
|
|
|
|
2015-10-22 17:44:10 +00:00
|
|
|
if (exception) {
|
|
|
|
return (json){"error", RJSStringForValue(_context, exception)};
|
|
|
|
}
|
2015-10-20 22:10:22 +00:00
|
|
|
|
2015-10-22 17:44:10 +00:00
|
|
|
RPCObjectID realmId = store_object(realmObject);
|
|
|
|
return (json){"result", realmId};
|
|
|
|
};
|
|
|
|
_requests["/begin_transaction"] = [=](const json dict) {
|
|
|
|
RPCObjectID realmId = dict["realmId"].get<RPCObjectID>();
|
|
|
|
RJSGetInternal<realm::SharedRealm *>(_objects[realmId])->get()->begin_transaction();
|
|
|
|
return nil;
|
|
|
|
};
|
|
|
|
_requests["/cancel_transaction"] = [=](const json dict) {
|
|
|
|
RPCObjectID realmId = dict["realmId"].get<RPCObjectID>();
|
|
|
|
RJSGetInternal<realm::SharedRealm *>(_objects[realmId])->get()->cancel_transaction();
|
|
|
|
return nil;
|
|
|
|
};
|
|
|
|
_requests["/commit_transaction"] = [=](const json dict) {
|
|
|
|
RPCObjectID realmId = dict["realmId"].get<RPCObjectID>();
|
|
|
|
RJSGetInternal<realm::SharedRealm *>(_objects[realmId])->get()->commit_transaction();
|
|
|
|
return nil;
|
|
|
|
};
|
|
|
|
_requests["/call_method"] = [=](const json dict) {
|
|
|
|
JSObjectRef object = _objects[dict["id"].get<RPCObjectID>()];
|
|
|
|
JSStringRef methodString = RJSStringForString(dict["name"].get<const char *>());
|
|
|
|
JSObjectRef function = RJSValidatedObjectProperty(_context, object, methodString);
|
|
|
|
JSStringRelease(methodString);
|
|
|
|
|
|
|
|
json args = dict["arguments"];
|
|
|
|
size_t argCount = args.size();
|
|
|
|
JSValueRef argValues[argCount];
|
|
|
|
for (size_t i = 0; i < argCount; i++) {
|
|
|
|
argValues[i] = deserialize_json_value(args[i]);
|
|
|
|
}
|
2015-10-15 10:00:13 +00:00
|
|
|
|
2015-10-22 17:44:10 +00:00
|
|
|
JSValueRef exception = NULL;
|
|
|
|
JSValueRef result = JSObjectCallAsFunction(_context, function, object, argCount, argValues, &exception);
|
2015-10-15 10:00:13 +00:00
|
|
|
|
2015-10-22 17:44:10 +00:00
|
|
|
if (exception) {
|
|
|
|
return (json){"error", RJSStringForValue(_context, exception)};
|
|
|
|
}
|
|
|
|
return (json){"result", serialize_json_value(result)};
|
|
|
|
};
|
|
|
|
_requests["/get_property"] = [=](const json dict) {
|
|
|
|
RPCObjectID oid = dict["id"].get<RPCObjectID>();
|
|
|
|
json name = dict["name"];
|
|
|
|
JSValueRef value = NULL;
|
|
|
|
JSValueRef exception = NULL;
|
|
|
|
|
|
|
|
if (name.is_number()) {
|
|
|
|
value = JSObjectGetPropertyAtIndex(_context, _objects[oid], name.get<unsigned int>(), &exception);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
JSStringRef propString = RJSStringForString(name.get<const char *>());
|
|
|
|
value = JSObjectGetProperty(_context, _objects[oid], propString, &exception);
|
|
|
|
JSStringRelease(propString);
|
|
|
|
}
|
2015-10-15 10:00:13 +00:00
|
|
|
|
2015-10-22 17:44:10 +00:00
|
|
|
if (exception) {
|
|
|
|
return (json){"error", RJSStringForValue(_context, exception)};
|
|
|
|
}
|
|
|
|
return (json){"result", serialize_json_value(value)};
|
|
|
|
};
|
|
|
|
_requests["/set_property"] = [=](const json dict) {
|
|
|
|
RPCObjectID oid = dict["id"].get<RPCObjectID>();
|
|
|
|
json name = dict["name"];
|
|
|
|
JSValueRef value = deserialize_json_value(dict["value"]);
|
|
|
|
JSValueRef exception = NULL;
|
|
|
|
|
|
|
|
if (name.is_number()) {
|
|
|
|
JSObjectSetPropertyAtIndex(_context, _objects[oid], name.get<unsigned int>(), value, &exception);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
JSStringRef propString = RJSStringForString(name.get<const char *>());
|
|
|
|
JSObjectSetProperty(_context, _objects[oid], propString, value, 0, &exception);
|
|
|
|
JSStringRelease(propString);
|
|
|
|
}
|
2015-10-15 10:00:13 +00:00
|
|
|
|
2015-10-22 17:44:10 +00:00
|
|
|
if (exception) {
|
|
|
|
return @{@"error": @(RJSStringForValue(_context, exception).c_str())};
|
|
|
|
}
|
|
|
|
return @{};
|
|
|
|
};
|
|
|
|
_requests["/dispose_object"] = [=](const json dict) {
|
|
|
|
RPCObjectID oid = dict["id"].get<RPCObjectID>();
|
|
|
|
JSValueUnprotect(_context, _objects[oid]);
|
|
|
|
_objects.erase(oid);
|
|
|
|
return nil;
|
|
|
|
};
|
|
|
|
_requests["/clear_test_state"] = [=](const json dict) {
|
|
|
|
for (auto object : _objects) {
|
|
|
|
// The session ID points to the Realm constructor object, which should remain.
|
|
|
|
if (object.first == _sessionID) {
|
|
|
|
continue;
|
2015-10-08 17:57:07 +00:00
|
|
|
}
|
2015-10-08 22:30:29 +00:00
|
|
|
|
2015-10-22 17:44:10 +00:00
|
|
|
JSValueUnprotect(_context, object.second);
|
|
|
|
_objects.erase(object.first);
|
|
|
|
}
|
|
|
|
JSGarbageCollect(_context);
|
|
|
|
[RealmJS clearTestState];
|
|
|
|
return nil;
|
|
|
|
};
|
|
|
|
}
|
2015-10-16 00:07:10 +00:00
|
|
|
|
2015-10-22 17:44:10 +00:00
|
|
|
RPCServer::~RPCServer() {
|
|
|
|
for (auto item : _objects) {
|
|
|
|
JSValueUnprotect(_context, item.second);
|
2015-10-08 17:57:07 +00:00
|
|
|
}
|
2015-10-22 17:44:10 +00:00
|
|
|
|
|
|
|
JSGlobalContextRelease(_context);
|
2015-10-08 17:57:07 +00:00
|
|
|
}
|
|
|
|
|
2015-10-22 17:44:10 +00:00
|
|
|
json RPCServer::perform_request(const std::string &name, const json args) {
|
2015-10-08 17:57:07 +00:00
|
|
|
// perform all realm ops on the main thread
|
2015-10-22 17:44:10 +00:00
|
|
|
RPCRequest action = _requests[name];
|
2015-10-16 00:07:10 +00:00
|
|
|
assert(action);
|
|
|
|
|
2015-10-22 17:44:10 +00:00
|
|
|
__block json response;
|
2015-10-08 17:57:07 +00:00
|
|
|
dispatch_sync(dispatch_get_main_queue(), ^{
|
2015-10-22 17:44:10 +00:00
|
|
|
if (_sessionID != args["sessionId"].get<RPCObjectID>()) {
|
|
|
|
response = {"error", "Invalid session ID"};
|
2015-10-20 22:10:22 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-10-08 18:23:42 +00:00
|
|
|
try {
|
|
|
|
response = action(args);
|
|
|
|
} catch (std::exception &exception) {
|
2015-10-22 17:44:10 +00:00
|
|
|
response = {"error", (std::string)"exception thrown: " + exception.what()};
|
2015-10-08 18:23:42 +00:00
|
|
|
}
|
2015-10-08 17:57:07 +00:00
|
|
|
});
|
2015-10-22 17:44:10 +00:00
|
|
|
return response ?: json();
|
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-08 18:23:42 +00:00
|
|
|
JSValueProtect(_context, object);
|
|
|
|
_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-08 18:23:42 +00:00
|
|
|
switch (JSValueGetType(_context, value)) {
|
2015-10-08 17:57:07 +00:00
|
|
|
case kJSTypeUndefined:
|
2015-10-22 17:44:10 +00:00
|
|
|
return {};
|
2015-10-08 17:57:07 +00:00
|
|
|
case kJSTypeNull:
|
2015-10-22 17:44:10 +00:00
|
|
|
return {"value", json(nullptr)};
|
2015-10-08 17:57:07 +00:00
|
|
|
case kJSTypeBoolean:
|
2015-10-22 17:44:10 +00:00
|
|
|
return {"value", JSValueToBoolean(_context, value)};
|
2015-10-08 17:57:07 +00:00
|
|
|
case kJSTypeNumber:
|
2015-10-22 17:44:10 +00:00
|
|
|
return {"value", JSValueToNumber(_context, value, NULL)};
|
2015-10-08 17:57:07 +00:00
|
|
|
case kJSTypeString:
|
2015-10-22 17:44:10 +00:00
|
|
|
return {"value", RJSStringForValue(_context, value).c_str()};
|
2015-10-08 17:57:07 +00:00
|
|
|
case kJSTypeObject:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2015-10-08 18:23:42 +00:00
|
|
|
JSObjectRef jsObject = JSValueToObject(_context, value, NULL);
|
2015-10-08 17:57:07 +00:00
|
|
|
|
2015-10-08 18:23:42 +00:00
|
|
|
if (JSValueIsObjectOfClass(_context, value, RJSObjectClass())) {
|
2015-10-08 17:57:07 +00:00
|
|
|
realm::Object *object = RJSGetInternal<realm::Object *>(jsObject);
|
2015-10-22 17:44:10 +00:00
|
|
|
return {
|
|
|
|
"type", RJSTypeGet(realm::PropertyTypeObject).c_str(),
|
|
|
|
"id", store_object(jsObject),
|
|
|
|
"schema", serialize_object_schema(object->object_schema)
|
2015-10-08 17:57:07 +00:00
|
|
|
};
|
|
|
|
}
|
2015-10-13 22:27:24 +00:00
|
|
|
else if (JSValueIsObjectOfClass(_context, value, RJSListClass())) {
|
2015-10-13 21:41:51 +00:00
|
|
|
realm::List *list = RJSGetInternal<realm::List *>(jsObject);
|
2015-10-22 17:44:10 +00:00
|
|
|
return {
|
|
|
|
"type", RJSTypeGet(realm::PropertyTypeArray),
|
|
|
|
"id", store_object(jsObject),
|
|
|
|
"size", list->link_view->size(),
|
|
|
|
"schema", serialize_object_schema(list->object_schema)
|
2015-10-08 17:57:07 +00:00
|
|
|
};
|
|
|
|
}
|
2015-10-08 18:23:42 +00:00
|
|
|
else if (JSValueIsObjectOfClass(_context, value, RJSResultsClass())) {
|
2015-10-08 17:57:07 +00:00
|
|
|
realm::Results *results = RJSGetInternal<realm::Results *>(jsObject);
|
2015-10-22 17:44:10 +00:00
|
|
|
return {
|
|
|
|
"type", RealmObjectTypesResults,
|
|
|
|
"id", store_object(jsObject),
|
|
|
|
"size", results->size(),
|
|
|
|
"schema", serialize_object_schema(results->object_schema)
|
2015-10-08 17:57:07 +00:00
|
|
|
};
|
|
|
|
}
|
2015-10-15 01:00:21 +00:00
|
|
|
else if (JSValueIsObjectOfClass(_context, value, RJSNotificationClass())) {
|
2015-10-22 17:44:10 +00:00
|
|
|
return {
|
|
|
|
"type", RealmObjectTypesNotification,
|
|
|
|
"id", store_object(jsObject),
|
2015-10-15 01:00:21 +00:00
|
|
|
};
|
|
|
|
}
|
2015-10-08 18:23:42 +00:00
|
|
|
else if (RJSIsValueArray(_context, value)) {
|
2015-10-13 22:25:06 +00:00
|
|
|
size_t length = RJSValidatedListLength(_context, jsObject);
|
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-22 17:44:10 +00:00
|
|
|
array.push_back(serialize_json_value(JSObjectGetPropertyAtIndex(_context, jsObject, i, NULL)));
|
2015-10-08 17:57:07 +00:00
|
|
|
}
|
2015-10-22 17:44:10 +00:00
|
|
|
return {"value", array};
|
2015-10-08 17:57:07 +00:00
|
|
|
}
|
2015-10-19 23:46:56 +00:00
|
|
|
else if (RJSIsValueDate(_context, value)) {
|
2015-10-22 17:44:10 +00:00
|
|
|
return {
|
|
|
|
"type", RJSTypeGet(realm::PropertyTypeDate),
|
|
|
|
"value", RJSValidatedValueToNumber(_context, value),
|
2015-10-19 23:46:56 +00:00
|
|
|
};
|
|
|
|
}
|
2015-10-22 17:44:10 +00:00
|
|
|
assert(0);
|
2015-10-08 17:57:07 +00:00
|
|
|
}
|
|
|
|
|
2015-10-22 17:44:10 +00:00
|
|
|
json RPCServer::serialize_object_schema(realm::ObjectSchema &objectSchema) {
|
|
|
|
json properties({});
|
2015-10-08 17:57:07 +00:00
|
|
|
for (realm::Property prop : objectSchema.properties) {
|
2015-10-22 17:44:10 +00:00
|
|
|
properties.push_back({
|
|
|
|
"name", prop.name,
|
|
|
|
"type", RJSTypeGet(prop.type),
|
|
|
|
});
|
2015-10-08 17:57:07 +00:00
|
|
|
}
|
|
|
|
|
2015-10-22 17:44:10 +00:00
|
|
|
return {
|
|
|
|
"name", objectSchema.name,
|
|
|
|
"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)
|
|
|
|
{
|
|
|
|
RPCObjectID oid = dict["id"].get<long>();
|
2015-10-08 17:57:07 +00:00
|
|
|
if (oid) {
|
2015-10-08 18:23:42 +00:00
|
|
|
return _objects[oid];
|
2015-10-08 17:57:07 +00:00
|
|
|
}
|
|
|
|
|
2015-10-15 01:00:21 +00:00
|
|
|
NSString *type = dict[@"type"];
|
2015-10-19 23:46:56 +00:00
|
|
|
id value = dict[@"value"];
|
|
|
|
|
2015-10-19 19:06:47 +00:00
|
|
|
if ([type isEqualToString:@(RealmObjectTypesFunction)]) {
|
2015-10-15 01:00:21 +00:00
|
|
|
// FIXME: Make this actually call the function by its id once we need it to.
|
|
|
|
JSStringRef jsBody = JSStringCreateWithUTF8CString("");
|
|
|
|
JSObjectRef jsFunction = JSObjectMakeFunction(_context, NULL, 0, NULL, jsBody, NULL, 1, NULL);
|
|
|
|
JSStringRelease(jsBody);
|
|
|
|
|
|
|
|
return jsFunction;
|
|
|
|
}
|
2015-10-19 23:46:56 +00:00
|
|
|
else if ([type isEqualToString:@(RJSTypeGet(realm::PropertyTypeDate).c_str())]) {
|
|
|
|
JSValueRef exception = NULL;
|
|
|
|
JSValueRef time = JSValueMakeNumber(_context, [value doubleValue]);
|
|
|
|
JSObjectRef date = JSObjectMakeDate(_context, 1, &time, &exception);
|
|
|
|
|
|
|
|
if (exception) {
|
|
|
|
throw RJSException(_context, exception);
|
|
|
|
}
|
|
|
|
return date;
|
|
|
|
}
|
2015-10-15 01:00:21 +00:00
|
|
|
|
2015-10-08 17:57:07 +00:00
|
|
|
if (!value) {
|
2015-10-08 18:23:42 +00:00
|
|
|
return JSValueMakeUndefined(_context);
|
2015-10-08 17:57:07 +00:00
|
|
|
}
|
|
|
|
else if ([value isKindOfClass:[NSNull class]]) {
|
2015-10-08 18:23:42 +00:00
|
|
|
return JSValueMakeNull(_context);
|
2015-10-08 17:57:07 +00:00
|
|
|
}
|
|
|
|
else if ([value isKindOfClass:[@YES class]]) {
|
2015-10-08 18:23:42 +00:00
|
|
|
return JSValueMakeBoolean(_context, [value boolValue]);
|
2015-10-08 17:57:07 +00:00
|
|
|
}
|
|
|
|
else if ([value isKindOfClass:[NSNumber class]]) {
|
2015-10-08 18:23:42 +00:00
|
|
|
return JSValueMakeNumber(_context, [value doubleValue]);
|
2015-10-08 17:57:07 +00:00
|
|
|
}
|
|
|
|
else if ([value isKindOfClass:[NSString class]]) {
|
2015-10-08 18:23:42 +00:00
|
|
|
return RJSValueForString(_context, std::string([value UTF8String]));
|
2015-10-08 17:57:07 +00:00
|
|
|
}
|
|
|
|
else if ([value isKindOfClass:[NSArray class]]) {
|
|
|
|
NSUInteger count = [value count];
|
|
|
|
JSValueRef jsValues[count];
|
|
|
|
|
|
|
|
for (NSUInteger i = 0; i < count; i++) {
|
2015-10-16 00:07:10 +00:00
|
|
|
jsValues[i] = [self deserializeDictionaryValue:value[i]];
|
2015-10-08 17:57:07 +00:00
|
|
|
}
|
|
|
|
|
2015-10-08 18:23:42 +00:00
|
|
|
return JSObjectMakeArray(_context, count, jsValues, NULL);
|
2015-10-08 17:57:07 +00:00
|
|
|
}
|
|
|
|
else if ([value isKindOfClass:[NSDictionary class]]) {
|
2015-10-08 18:23:42 +00:00
|
|
|
JSObjectRef jsObject = JSObjectMake(_context, NULL, NULL);
|
2015-10-08 17:57:07 +00:00
|
|
|
|
|
|
|
for (NSString *key in value) {
|
2015-10-16 00:07:10 +00:00
|
|
|
JSValueRef jsValue = [self deserializeDictionaryValue:value[key]];
|
2015-10-08 17:57:07 +00:00
|
|
|
JSStringRef jsKey = JSStringCreateWithCFString((__bridge CFStringRef)key);
|
|
|
|
|
2015-10-08 18:23:42 +00:00
|
|
|
JSObjectSetProperty(_context, jsObject, jsKey, jsValue, 0, NULL);
|
2015-10-08 17:57:07 +00:00
|
|
|
JSStringRelease(jsKey);
|
|
|
|
}
|
|
|
|
|
|
|
|
return jsObject;
|
|
|
|
}
|
|
|
|
|
2015-10-08 18:23:42 +00:00
|
|
|
return JSValueMakeUndefined(_context);
|
2015-10-08 17:57:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@end
|