realm-js/src/RealmRPC.mm

351 lines
14 KiB
Plaintext
Raw Normal View History

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.
//
////////////////////////////////////////////////////////////////////////////
#import "RealmRPC.h"
#import <JavaScriptCore/JavaScriptCore.h>
#include <map>
#include <string>
#include "RealmJS.h"
#include "RJSObject.hpp"
#include "RJSResults.hpp"
#include "RJSArray.hpp"
#include "RJSRealm.hpp"
#include "RJSUtil.hpp"
#include "object_accessor.hpp"
#include "shared_realm.hpp"
#include "results.hpp"
2015-10-08 18:23:42 +00:00
using RPCObjectID = u_int64_t;
2015-10-08 17:57:07 +00:00
using RPCRequest = std::function<NSDictionary *(NSDictionary *dictionary)>;
@implementation RJSRPCServer {
2015-10-08 18:23:42 +00:00
JSGlobalContextRef _context;
std::map<std::string, RPCRequest> _requests;
std::map<RPCObjectID, JSObjectRef> _objects;
2015-10-08 17:57:07 +00:00
}
- (instancetype)init {
self = [super init];
if (self) {
2015-10-08 18:23:42 +00:00
_context = JSGlobalContextCreate(NULL);
_requests["/create_realm"] = [=](NSDictionary *dict) {
// We should have a method for serializing schema rather than relying on JSValue
2015-10-08 17:57:07 +00:00
JSValueRef value = [[JSValue valueWithObject:dict
2015-10-08 18:23:42 +00:00
inContext:[JSContext contextWithJSGlobalContextRef:_context]] JSValueRef];
JSValueRef ex = NULL;
RPCObjectID realmId = [self storeObject:RealmConstructor(_context, NULL, 1, &value, &ex)];
if (ex) {
return @{@"error": @(RJSStringForValue(_context, ex).c_str())};
}
2015-10-08 17:57:07 +00:00
return @{@"result": @(realmId)};
};
2015-10-08 18:23:42 +00:00
_requests["/begin_transaction"] = [=](NSDictionary *dict) {
RPCObjectID realmId = [dict[@"realmId"] unsignedLongValue];
RJSGetInternal<realm::SharedRealm *>(_objects[realmId])->get()->begin_transaction();
2015-10-08 17:57:07 +00:00
return @{};
};
2015-10-08 18:23:42 +00:00
_requests["/cancel_transaction"] = [=](NSDictionary *dict) {
RPCObjectID realmId = [dict[@"realmId"] unsignedLongValue];
RJSGetInternal<realm::SharedRealm *>(_objects[realmId])->get()->cancel_transaction();
2015-10-08 17:57:07 +00:00
return @{};
};
2015-10-08 18:23:42 +00:00
_requests["/commit_transaction"] = [=](NSDictionary *dict) {
RPCObjectID realmId = [dict[@"realmId"] unsignedLongValue];
RJSGetInternal<realm::SharedRealm *>(_objects[realmId])->get()->commit_transaction();
2015-10-08 17:57:07 +00:00
return @{};
};
2015-10-08 18:23:42 +00:00
_requests["/call_realm_method"] = [=](NSDictionary *dict) {
2015-10-08 17:57:07 +00:00
NSString *name = dict[@"name"];
return [self performObjectMethod:name.UTF8String
classMethods:RJSRealmFuncs
args:dict[@"arguments"]
2015-10-08 18:23:42 +00:00
objectId:[dict[@"realmId"] unsignedLongValue]];
2015-10-08 17:57:07 +00:00
};
2015-10-08 18:23:42 +00:00
_requests["/get_property"] = [=](NSDictionary *dict) {
2015-10-08 17:57:07 +00:00
JSValueRef exception = NULL;
NSString *name = dict[@"name"];
JSStringRef propString = RJSStringForString(name.UTF8String);
2015-10-08 18:23:42 +00:00
RPCObjectID objectId = [dict[@"objectId"] unsignedLongValue];
JSValueRef propertyValue = ObjectGetProperty(_context, _objects[objectId], propString, &exception);
2015-10-08 17:57:07 +00:00
JSStringRelease(propString);
if (exception) {
2015-10-08 18:23:42 +00:00
return @{@"error": @(RJSStringForValue(_context, exception).c_str())};
2015-10-08 17:57:07 +00:00
}
return @{@"result": [self resultForJSValue:propertyValue]};
};
2015-10-08 18:23:42 +00:00
_requests["/set_property"] = [=](NSDictionary *dict) {
2015-10-08 17:57:07 +00:00
JSStringRef propString = RJSStringForString([dict[@"name"] UTF8String]);
2015-10-08 18:23:42 +00:00
RPCObjectID realmId = [dict[@"objectId"] unsignedLongValue];
2015-10-08 17:57:07 +00:00
JSValueRef value = [self valueFromDictionary:dict[@"value"]];
JSValueRef exception = NULL;
2015-10-08 18:23:42 +00:00
ObjectSetProperty(_context, _objects[realmId], propString, value, &exception);
2015-10-08 17:57:07 +00:00
JSStringRelease(propString);
2015-10-08 18:23:42 +00:00
return exception ? @{@"error": @(RJSStringForValue(_context, exception).c_str())} : @{};
2015-10-08 17:57:07 +00:00
};
2015-10-08 18:23:42 +00:00
_requests["/dispose_object"] = [=](NSDictionary *dict) {
RPCObjectID oid = [dict[@"id"] unsignedLongValue];
JSValueUnprotect(_context, _objects[oid]);
_objects.erase(oid);
2015-10-08 17:57:07 +00:00
return @{};
};
2015-10-08 18:23:42 +00:00
_requests["/get_results_size"] = [=](NSDictionary *dict) {
RPCObjectID resultsId = [dict[@"resultsId"] unsignedLongValue];
2015-10-08 17:57:07 +00:00
JSValueRef exception = NULL;
static JSStringRef lengthPropertyName = JSStringCreateWithUTF8CString("length");
2015-10-08 18:23:42 +00:00
JSValueRef lengthValue = ResultsGetProperty(_context, _objects[resultsId], lengthPropertyName, &exception);
size_t length = JSValueToNumber(_context, lengthValue, &exception);
if (exception) {
return @{@"error": @(RJSStringForValue(_context, exception).c_str())};
}
return @{@"result": @(length)};
2015-10-08 17:57:07 +00:00
};
2015-10-08 18:23:42 +00:00
_requests["/get_results_item"] = [=](NSDictionary *dict) {
RPCObjectID resultsId = [dict[@"resultsId"] unsignedLongValue];
2015-10-08 17:57:07 +00:00
long index = [dict[@"index"] longValue];
JSValueRef exception = NULL;
JSStringRef indexPropertyName = JSStringCreateWithUTF8CString(std::to_string(index).c_str());
2015-10-08 18:23:42 +00:00
JSValueRef objectValue = ResultsGetProperty(_context, _objects[resultsId], indexPropertyName, &exception);
2015-10-08 17:57:07 +00:00
JSStringRelease(indexPropertyName);
if (exception) {
2015-10-08 18:23:42 +00:00
return @{@"error": @(RJSStringForValue(_context, exception).c_str())};
2015-10-08 17:57:07 +00:00
}
return @{@"result": [self resultForJSValue:objectValue]};
};
2015-10-08 18:23:42 +00:00
_requests["/get_list_size"] = [=](NSDictionary *dict) {
RPCObjectID listId = [dict[@"listId"] unsignedLongValue];
2015-10-08 17:57:07 +00:00
JSValueRef exception = NULL;
static JSStringRef lengthPropertyName = JSStringCreateWithUTF8CString("length");
2015-10-08 18:23:42 +00:00
JSValueRef lengthValue = ArrayGetProperty(_context, _objects[listId], lengthPropertyName, &exception);
size_t length = JSValueToNumber(_context, lengthValue, &exception);
if (exception) {
return @{@"error": @(RJSStringForValue(_context, exception).c_str())};
}
return @{@"result": @(length)};
2015-10-08 17:57:07 +00:00
};
2015-10-08 18:23:42 +00:00
_requests["/get_list_item"] = [=](NSDictionary *dict) {
RPCObjectID listId = [dict[@"listId"] unsignedLongValue];
2015-10-08 17:57:07 +00:00
long index = [dict[@"index"] longValue];
JSValueRef exception = NULL;
JSStringRef indexPropertyName = JSStringCreateWithUTF8CString(std::to_string(index).c_str());
2015-10-08 18:23:42 +00:00
JSValueRef objectValue = ArrayGetProperty(_context, _objects[listId], indexPropertyName, &exception);
2015-10-08 17:57:07 +00:00
JSStringRelease(indexPropertyName);
if (exception) {
2015-10-08 18:23:42 +00:00
return @{@"error": @(RJSStringForValue(_context, exception).c_str())};
2015-10-08 17:57:07 +00:00
}
return @{@"result": [self resultForJSValue:objectValue]};
};
2015-10-08 18:23:42 +00:00
_requests["/call_list_method"] = [=](NSDictionary *dict) {
2015-10-08 17:57:07 +00:00
NSString *name = dict[@"name"];
return [self performObjectMethod:name.UTF8String
classMethods:RJSArrayFuncs
args:dict[@"arguments"]
2015-10-08 18:23:42 +00:00
objectId:[dict[@"listId"] unsignedLongValue]];
2015-10-08 17:57:07 +00:00
};
}
return self;
}
- (NSDictionary *)performRequest:(NSString *)name args:(NSDictionary *)args {
// perform all realm ops on the main thread
2015-10-08 18:23:42 +00:00
RPCRequest action = _requests[name.UTF8String];
2015-10-08 17:57:07 +00:00
__block id response;
dispatch_sync(dispatch_get_main_queue(), ^{
2015-10-08 18:23:42 +00:00
try {
response = action(args);
} catch (std::exception &exception) {
response = @{@"error": [@"exception thrown: " stringByAppendingString:@(exception.what())]};
}
2015-10-08 17:57:07 +00:00
});
return response;
}
- (NSDictionary *)performObjectMethod:(const char *)name
classMethods:(const JSStaticFunction [])methods
args:(NSArray *)args
objectId:(RPCObjectID)oid {
NSUInteger count = args.count;
JSValueRef argValues[count];
for (NSUInteger i = 0; i < count; i++) {
argValues[i] = [self valueFromDictionary:args[i]];
}
size_t index = 0;
while (methods[index].name) {
if (!strcmp(methods[index].name, name)) {
JSValueRef ex = NULL;
2015-10-08 18:23:42 +00:00
JSValueRef ret = methods[index].callAsFunction(_context, NULL, _objects[oid], count, argValues, &ex);
2015-10-08 17:57:07 +00:00
if (ex) {
2015-10-08 18:23:42 +00:00
return @{@"error": @(RJSStringForValue(_context, ex).c_str())};
2015-10-08 17:57:07 +00:00
}
return @{@"result": [self resultForJSValue:ret]};
}
index++;
}
return @{@"error": @"invalid method"};
}
- (RPCObjectID)storeObject:(JSObjectRef)object {
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;
}
- (NSDictionary *)resultForJSValue:(JSValueRef)value {
2015-10-08 18:23:42 +00:00
switch (JSValueGetType(_context, value)) {
2015-10-08 17:57:07 +00:00
case kJSTypeUndefined:
return @{};
case kJSTypeNull:
return @{@"value": [NSNull null]};
case kJSTypeBoolean:
2015-10-08 18:23:42 +00:00
return @{@"value": @(JSValueToBoolean(_context, value))};
2015-10-08 17:57:07 +00:00
case kJSTypeNumber:
2015-10-08 18:23:42 +00:00
return @{@"value": @(JSValueToNumber(_context, value, NULL))};
2015-10-08 17:57:07 +00:00
case kJSTypeString:
2015-10-08 18:23:42 +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
RPCObjectID oid = [self storeObject:jsObject];
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);
return @{
@"type": @(RJSTypeGet(realm::PropertyTypeObject).c_str()),
@"id": @(oid),
@"schema": [self objectSchemaToJSONObject:object->object_schema]
};
}
2015-10-08 18:23:42 +00:00
else if (JSValueIsObjectOfClass(_context, value, RJSArrayClass())) {
2015-10-08 17:57:07 +00:00
realm::ObjectArray *array = RJSGetInternal<realm::ObjectArray *>(jsObject);
return @{
@"type": @(RJSTypeGet(realm::PropertyTypeArray).c_str()),
@"id": @(oid),
@"size": @(array->link_view->size()),
@"schema": [self objectSchemaToJSONObject:array->object_schema]
};
}
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);
return @{
@"type": @"ObjectTypesRESULTS",
@"resultsId": @(oid),
@"size": @(results->size()),
@"schema": [self objectSchemaToJSONObject:results->object_schema]
};
}
2015-10-08 18:23:42 +00:00
else if (RJSIsValueArray(_context, value)) {
size_t length = RJSValidatedArrayLength(_context, jsObject);
2015-10-08 17:57:07 +00:00
NSMutableArray *array = [NSMutableArray new];
for (unsigned int i = 0; i < length; i++) {
2015-10-08 18:23:42 +00:00
[array addObject:[self resultForJSValue:JSObjectGetPropertyAtIndex(_context, jsObject, i, NULL)]];
2015-10-08 17:57:07 +00:00
}
return @{@"value": array};
}
else {
assert(0);
}
}
- (NSDictionary *)objectSchemaToJSONObject:(realm::ObjectSchema &)objectSchema {
NSMutableArray *properties = [[NSMutableArray alloc] init];
for (realm::Property prop : objectSchema.properties) {
NSDictionary *dict = @{
@"name": @(prop.name.c_str()),
@"type": @(RJSTypeGet(prop.type).c_str()),
};
[properties addObject:dict];
}
return @{
@"name": @(objectSchema.name.c_str()),
@"properties": properties,
};
}
- (JSValueRef)valueFromDictionary:(NSDictionary *)dict {
RPCObjectID oid = [dict[@"id"] longValue];
if (oid) {
2015-10-08 18:23:42 +00:00
return _objects[oid];
2015-10-08 17:57:07 +00:00
}
id value = dict[@"value"];
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++) {
jsValues[i] = [self valueFromDictionary:value[i]];
}
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) {
JSValueRef jsValue = [self valueFromDictionary:value[key]];
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