From 44eea1e036a9a359ec2013f0ccffde1f71d05f59 Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Wed, 14 Oct 2015 16:05:49 -0700 Subject: [PATCH 01/15] Minor syntax improvements to JS shim --- lib/rpc.js | 2 +- lib/util.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/rpc.js b/lib/rpc.js index 7bf4f65f..269f9f91 100644 --- a/lib/rpc.js +++ b/lib/rpc.js @@ -128,7 +128,7 @@ function serialize(realmId, value) { throw new Error('Unable to serialize value from another Realm'); } - return {id: id}; + return {id}; } if (Array.isArray(value)) { diff --git a/lib/util.js b/lib/util.js index bd18c4ba..51f21445 100644 --- a/lib/util.js +++ b/lib/util.js @@ -7,8 +7,8 @@ let resizeListKey = exports.resizeListKey = Symbol(); exports.createList = createList; function createList(prototype, getterForLength, getterForIndex, setterForIndex) { - var list = prototype ? Object.create(prototype) : {}; - var size = 0; + let list = prototype ? Object.create(prototype) : {}; + let size = 0; Object.defineProperty(list, 'length', {get: getterForLength}); From b985e1f0a652f596e12bb9dd7f59545d092c0bf0 Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Wed, 14 Oct 2015 16:06:20 -0700 Subject: [PATCH 02/15] Add realm.close() method to JS shim That was easy! --- lib/realm.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/realm.js b/lib/realm.js index ca56e2f6..2db50527 100644 --- a/lib/realm.js +++ b/lib/realm.js @@ -63,6 +63,7 @@ class Realm { } [ + 'close', 'create', 'delete', 'deleteAll', From 78ec67cd6c44060d2730d2d2cb790b85d5aae409 Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Wed, 14 Oct 2015 18:00:21 -0700 Subject: [PATCH 03/15] Hacky support for adding notifications in Chrome Since notifications are called synchronously after a write, we fake it by calling them manually for now. The future plan will be more involved, so some of that is stubbed out. --- lib/notifications.js | 19 +++++++++++++++++++ lib/realm.js | 31 +++++++++++++++++++++++++++---- lib/rpc.js | 4 ++++ src/RealmRPC.mm | 17 +++++++++++++++++ 4 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 lib/notifications.js diff --git a/lib/notifications.js b/lib/notifications.js new file mode 100644 index 00000000..cdd48f54 --- /dev/null +++ b/lib/notifications.js @@ -0,0 +1,19 @@ +'use strict'; + +let util = require('./util'); + +let idKey = util.idKey; +let realmKey = util.realmKey; + +exports.create = create; + +function create(realmId, info) { + let notification = new Notification(); + + notification[realmKey] = realmId; + notification[idKey] = info.id; + + return notification; +} + +class Notification {} diff --git a/lib/realm.js b/lib/realm.js index 2db50527..6f1a3665 100644 --- a/lib/realm.js +++ b/lib/realm.js @@ -2,24 +2,28 @@ let lists = require('./lists'); let objects = require('./objects'); +let notifications = require('./notifications'); let results = require('./results'); let rpc = require('./rpc'); let types = require('./types'); let util = require('./util'); let realmKey = util.realmKey; +let notificationsKey = Symbol(); +let notificationCallbackKey = Symbol(); // TODO: DATA rpc.registerTypeConverter(types.DATE, (_, info) => new Date(info.value)); rpc.registerTypeConverter(types.LIST, lists.create); rpc.registerTypeConverter(types.OBJECT, objects.create); +rpc.registerTypeConverter('ObjectTypesNOTIFICATION', notifications.create); rpc.registerTypeConverter('ObjectTypesRESULTS', results.create); class Realm { constructor(config) { let schema = typeof config == 'object' && config.schema; let constructors = {}; - + for (let i = 0, len = schema ? schema.length : 0; i < len; i++) { let item = schema[i]; let proto = item.prototype; @@ -29,14 +33,28 @@ class Realm { constructors[proto.schema.name] = item; } } - + let realmId = this[realmKey] = rpc.createRealm(Array.from(arguments)); - + objects.registerConstructors(realmId, constructors); + + this[notificationsKey] = []; } addNotification(callback) { - // TODO + let realmId = this[realmKey]; + + if (!realmId) { + throw new TypeError('addNotification method was not called on a Realm object!'); + } + if (typeof callback != 'function') { + throw new Error('Realm.addNotification must be passed function!'); + } + + let notification = rpc.callRealmMethod(realmId, 'addNotification', [callback]); + notification[notificationCallbackKey] = callback; + + this[notificationsKey].push(notification); } write(callback) { @@ -59,6 +77,11 @@ class Realm { } rpc.commitTransaction(realmId); + + for (let notification of this[notificationsKey]) { + let callback = notification[notificationCallbackKey]; + callback(this, 'DidChangeNotification'); + } } } diff --git a/lib/rpc.js b/lib/rpc.js index 269f9f91..c91c8f49 100644 --- a/lib/rpc.js +++ b/lib/rpc.js @@ -118,6 +118,10 @@ function clearTestState() { } function serialize(realmId, value) { + if (typeof value == 'function') { + return {type: 'ObjectTypesFUNCTION'}; + } + if (!value || typeof value != 'object') { return {value: value}; } diff --git a/src/RealmRPC.mm b/src/RealmRPC.mm index b6c7870f..cc357751 100644 --- a/src/RealmRPC.mm +++ b/src/RealmRPC.mm @@ -317,6 +317,13 @@ using RPCRequest = std::function; @"schema": [self objectSchemaToJSONObject:results->object_schema] }; } + else if (JSValueIsObjectOfClass(_context, value, RJSNotificationClass())) { + RPCObjectID oid = [self storeObject:jsObject]; + return @{ + @"type": @"ObjectTypesNOTIFICATION", + @"id": @(oid), + }; + } else if (RJSIsValueArray(_context, value)) { size_t length = RJSValidatedListLength(_context, jsObject); NSMutableArray *array = [NSMutableArray new]; @@ -354,6 +361,16 @@ using RPCRequest = std::function; return _objects[oid]; } + NSString *type = dict[@"type"]; + if ([type isEqualToString:@"ObjectTypesFUNCTION"]) { + // 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; + } + id value = dict[@"value"]; if (!value) { return JSValueMakeUndefined(_context); From 5f52d0715425d7495ca69249f287621d90a8b84a Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Wed, 14 Oct 2015 18:53:37 -0700 Subject: [PATCH 04/15] Add support for Results methods in Chrome Which only consists of sortByProperty --- lib/results.js | 20 +++++++++++++++++++- lib/rpc.js | 42 +++++++++++++++++++++++++++--------------- src/RJSResults.hpp | 1 + src/RJSResults.mm | 10 +++++----- src/RealmRPC.mm | 7 +++++++ 5 files changed, 59 insertions(+), 21 deletions(-) diff --git a/lib/results.js b/lib/results.js index ca22aabb..8154f6f6 100644 --- a/lib/results.js +++ b/lib/results.js @@ -6,11 +6,29 @@ let util = require('./util'); let idKey = util.idKey; let realmKey = util.realmKey; let resizeListKey = util.resizeListKey; +let prototype = {}; exports.create = create; +[ + 'sortByProperty', +].forEach(function(name) { + Object.defineProperty(prototype, name, { + value: function() { + let resultsId = this[idKey]; + let realmId = this[realmKey]; + + if (!resultsId || !realmId) { + throw new TypeError(name + ' method was not called on Results!'); + } + + return rpc.callListMethod(realmId, resultsId, name, Array.from(arguments)); + } + }); +}); + function create(realmId, info) { - let results = util.createList(null, getterForLength, getterForIndex); + let results = util.createList(prototype, getterForLength, getterForIndex); let size = info.size; results[realmKey] = realmId; diff --git a/lib/rpc.js b/lib/rpc.js index c91c8f49..d567dc42 100644 --- a/lib/rpc.js +++ b/lib/rpc.js @@ -17,27 +17,30 @@ if (XMLHttpRequest.__proto__ != window.XMLHttpRequestEventTarget) { window.XMLHttpRequest = override; } -exports.registerTypeConverter = registerTypeConverter; +module.exports = { + registerTypeConverter, -exports.createRealm = createRealm; -exports.callRealmMethod = callRealmMethod; + createRealm, + callRealmMethod, -exports.getObjectProperty = getObjectProperty; -exports.setObjectProperty = setObjectProperty; + getObjectProperty, + setObjectProperty, -exports.getListItem = getListItem; -exports.setListItem = setListItem; -exports.getListSize = getListSize; -exports.callListMethod = callListMethod; + getListItem, + setListItem, + getListSize, + callListMethod, -exports.getResultsItem = getResultsItem; -exports.getResultsSize = getResultsSize; + getResultsItem, + getResultsSize, + callResultsMethod, -exports.beginTransaction = beginTransaction; -exports.cancelTransaction = cancelTransaction; -exports.commitTransaction = commitTransaction; + beginTransaction, + cancelTransaction, + commitTransaction, -exports.clearTestState = clearTestState; + clearTestState, +}; function registerTypeConverter(type, handler) { typeConverters[type] = handler; @@ -101,6 +104,15 @@ function getResultsSize(realmId, resultsId) { return sendRequest('get_results_size', {realmId, resultsId}); } +function callResultsMethod(realmId, resultsId, name, args) { + if (args) { + args = args.map((arg) => serialize(realmId, arg)); + } + + let result = sendRequest('call_results_method', {realmId, resultsId, name, arguments: args}); + return deserialize(realmId, result); +} + function beginTransaction(realmId) { sendRequest('begin_transaction', {realmId}); } diff --git a/src/RJSResults.hpp b/src/RJSResults.hpp index 5ffb0d24..62996604 100644 --- a/src/RJSResults.hpp +++ b/src/RJSResults.hpp @@ -23,6 +23,7 @@ namespace realm { typedef std::shared_ptr SharedRealm; } +extern const JSStaticFunction RJSResultsFuncs[]; JSClassRef RJSResultsClass(); JSObjectRef RJSResultsCreate(JSContextRef ctx, realm::SharedRealm realm, std::string className); JSObjectRef RJSResultsCreate(JSContextRef ctx, realm::SharedRealm realm, std::string className, std::string query); diff --git a/src/RJSResults.mm b/src/RJSResults.mm index 594d48de..1ed15eab 100644 --- a/src/RJSResults.mm +++ b/src/RJSResults.mm @@ -118,12 +118,12 @@ JSObjectRef RJSResultsCreate(JSContextRef ctx, SharedRealm realm, std::string cl return RJSWrapObject(ctx, RJSResultsClass(), new Results(realm, *object_schema, std::move(query))); } +const JSStaticFunction RJSResultsFuncs[] = { + {"sortByProperty", SortByProperty, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, + {NULL, NULL}, +}; JSClassRef RJSResultsClass() { - const JSStaticFunction resultsFuncs[] = { - {"sortByProperty", SortByProperty, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, - {NULL, NULL}, - }; - static JSClassRef s_objectClass = RJSCreateWrapperClass("Results", ResultsGetProperty, NULL, resultsFuncs, ResultsPropertyNames); + static JSClassRef s_objectClass = RJSCreateWrapperClass("Results", ResultsGetProperty, NULL, RJSResultsFuncs, ResultsPropertyNames); return s_objectClass; } diff --git a/src/RealmRPC.mm b/src/RealmRPC.mm index cc357751..53161a33 100644 --- a/src/RealmRPC.mm +++ b/src/RealmRPC.mm @@ -169,6 +169,13 @@ using RPCRequest = std::function; } return @{@"result": @(length)}; }; + _requests["/call_results_method"] = [=](NSDictionary *dict) { + NSString *name = dict[@"name"]; + return [self performObjectMethod:name.UTF8String + classMethods:RJSResultsFuncs + args:dict[@"arguments"] + objectId:[dict[@"resultsId"] unsignedLongValue]]; + }; _requests["/get_list_item"] = [=](NSDictionary *dict) { RPCObjectID listId = [dict[@"listId"] unsignedLongValue]; long index = [dict[@"index"] longValue]; From d482ea82e743159f10ac44959b3988266130fd7d Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Mon, 19 Oct 2015 12:05:41 -0700 Subject: [PATCH 05/15] Error messages deserve good grammar --- lib/realm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/realm.js b/lib/realm.js index 6f1a3665..0c4b072c 100644 --- a/lib/realm.js +++ b/lib/realm.js @@ -48,7 +48,7 @@ class Realm { throw new TypeError('addNotification method was not called on a Realm object!'); } if (typeof callback != 'function') { - throw new Error('Realm.addNotification must be passed function!'); + throw new Error('Realm.addNotification must be passed a function!'); } let notification = rpc.callRealmMethod(realmId, 'addNotification', [callback]); From 4c0cc578d5242e2a03f75e24dbef81baeea90ec9 Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Mon, 19 Oct 2015 12:06:47 -0700 Subject: [PATCH 06/15] Generalize calling methods through the RPC --- lib/internal-types.js | 16 ++++++++++ lib/keys.js | 8 +++++ lib/lists.js | 47 ++++++++---------------------- lib/objects.js | 19 ++++++------ lib/realm.js | 34 +++++++++------------- lib/results.js | 42 +++++++------------------- lib/rpc.js | 66 +++++++++++++++-------------------------- lib/types.js | 5 +++- lib/util.js | 51 +++++++++++++++++++++++++++----- src/RealmRPC.mm | 68 +++++++++++++++++++++---------------------- 10 files changed, 174 insertions(+), 182 deletions(-) create mode 100644 lib/internal-types.js create mode 100644 lib/keys.js diff --git a/lib/internal-types.js b/lib/internal-types.js new file mode 100644 index 00000000..0d494a5a --- /dev/null +++ b/lib/internal-types.js @@ -0,0 +1,16 @@ +'use strict'; + +let types = {}; + +[ + 'FUNCTION', + 'NOTIFICATION', + 'REALM', + 'RESULTS', +].forEach(function(type) { + Object.defineProperty(types, type, { + value: 'ObjectTypes' + type, + }); +}); + +module.exports = Object.freeze(types); diff --git a/lib/keys.js b/lib/keys.js new file mode 100644 index 00000000..c33fd42f --- /dev/null +++ b/lib/keys.js @@ -0,0 +1,8 @@ +'use strict'; + +module.exports = Object.freeze({ + id: Symbol(), + realm: Symbol(), + resize: Symbol(), + type: Symbol(), +}); diff --git a/lib/lists.js b/lib/lists.js index 9e42d804..536d9e8c 100644 --- a/lib/lists.js +++ b/lib/lists.js @@ -1,63 +1,40 @@ 'use strict'; +let keys = require('./keys'); let rpc = require('./rpc'); +let types = require('./types'); let util = require('./util'); -let idKey = util.idKey; -let realmKey = util.realmKey; -let resizeListKey = util.resizeListKey; -let prototype = {}; +module.exports = { + create, +}; -exports.create = create; +class List {} -[ +util.createMethods(List.prototype, types.LIST, [ 'pop', 'shift', 'push', 'unshift', 'splice', -].forEach(function(name) { - Object.defineProperty(prototype, name, { - value: function() { - let listId = this[idKey]; - let realmId = this[realmKey]; - - if (!listId || !realmId) { - throw new TypeError(name + ' method was not called on a List!'); - } - - let result = rpc.callListMethod(realmId, listId, name, Array.from(arguments)); - this[resizeListKey](); - - return result; - } - }); -}); +], true); function create(realmId, info) { - let list = util.createList(prototype, getterForLength, getterForIndex, setterForIndex); - let size = info.size; - - list[realmKey] = realmId; - list[idKey] = info.id; - - list[resizeListKey](size); - - return list; + return util.createList(List.prototype, realmId, info, getterForLength, getterForIndex, setterForIndex); } function getterForLength() { - return rpc.getListSize(this[realmKey], this[idKey]); + return rpc.getListSize(this[keys.realm], this[keys.id]); } function getterForIndex(index) { return function() { - return rpc.getListItem(this[realmKey], this[idKey], index); + return rpc.getListItem(this[keys.realm], this[keys.id], index); }; } function setterForIndex(index) { return function(value) { - rpc.setListItem(this[realmKey], this[idKey], index, value); + rpc.setListItem(this[keys.realm], this[keys.id], index, value); }; } diff --git a/lib/objects.js b/lib/objects.js index fa4a5ac2..c5737393 100644 --- a/lib/objects.js +++ b/lib/objects.js @@ -1,14 +1,14 @@ 'use strict'; +let keys = require('./keys'); let rpc = require('./rpc'); -let util = require('./util'); -let idKey = util.idKey; -let realmKey = util.realmKey; let registeredConstructors = {}; -exports.create = create; -exports.registerConstructors = registerConstructors; +module.exports = { + create, + registerConstructors, +}; function create(realmId, info) { let schema = info.schema; @@ -16,8 +16,9 @@ function create(realmId, info) { let object = constructor ? Object.create(constructor.prototype) : {}; let props = {}; - object[realmKey] = realmId; - object[idKey] = info.id; + object[keys.realm] = realmId; + object[keys.id] = info.id; + object[keys.type] = info.type; for (let prop of schema.properties) { let name = prop.name; @@ -39,12 +40,12 @@ function registerConstructors(realmId, constructors) { function getterForProperty(name) { return function() { - return rpc.getObjectProperty(this[realmKey], this[idKey], name); + return rpc.getObjectProperty(this[keys.realm], this[keys.id], name); }; } function setterForProperty(name) { return function(value) { - rpc.setObjectProperty(this[realmKey], this[idKey], name, value); + rpc.setObjectProperty(this[keys.realm], this[keys.id], name, value); }; } diff --git a/lib/realm.js b/lib/realm.js index 0c4b072c..6f9fb182 100644 --- a/lib/realm.js +++ b/lib/realm.js @@ -1,5 +1,7 @@ 'use strict'; +let internal = require('./internal-types'); +let keys = require('./keys'); let lists = require('./lists'); let objects = require('./objects'); let notifications = require('./notifications'); @@ -8,7 +10,6 @@ let rpc = require('./rpc'); let types = require('./types'); let util = require('./util'); -let realmKey = util.realmKey; let notificationsKey = Symbol(); let notificationCallbackKey = Symbol(); @@ -16,8 +17,8 @@ let notificationCallbackKey = Symbol(); rpc.registerTypeConverter(types.DATE, (_, info) => new Date(info.value)); rpc.registerTypeConverter(types.LIST, lists.create); rpc.registerTypeConverter(types.OBJECT, objects.create); -rpc.registerTypeConverter('ObjectTypesNOTIFICATION', notifications.create); -rpc.registerTypeConverter('ObjectTypesRESULTS', results.create); +rpc.registerTypeConverter(internal.NOTIFICATION, notifications.create); +rpc.registerTypeConverter(internal.RESULTS, results.create); class Realm { constructor(config) { @@ -34,15 +35,18 @@ class Realm { } } - let realmId = this[realmKey] = rpc.createRealm(Array.from(arguments)); + let realmId = rpc.createRealm(Array.from(arguments)); objects.registerConstructors(realmId, constructors); + this[keys.id] = realmId; + this[keys.realm] = realmId; + this[keys.type] = internal.REALM; this[notificationsKey] = []; } addNotification(callback) { - let realmId = this[realmKey]; + let realmId = this[keys.realm]; if (!realmId) { throw new TypeError('addNotification method was not called on a Realm object!'); @@ -51,14 +55,14 @@ class Realm { throw new Error('Realm.addNotification must be passed a function!'); } - let notification = rpc.callRealmMethod(realmId, 'addNotification', [callback]); + let notification = rpc.callMethod(realmId, realmId, internal.REALM, 'addNotification', [callback]); notification[notificationCallbackKey] = callback; this[notificationsKey].push(notification); } write(callback) { - let realmId = this[realmKey]; + let realmId = this[keys.realm]; if (!realmId) { throw new TypeError('write method was not called on a Realm object!'); @@ -85,25 +89,13 @@ class Realm { } } -[ +util.createMethods(Realm.prototype, internal.REALM, [ 'close', 'create', 'delete', 'deleteAll', 'objects', -].forEach(function(name) { - Object.defineProperty(Realm.prototype, name, { - value: function() { - let realmId = this[realmKey]; - - if (!realmId) { - throw new TypeError(name + ' method was not called on a Realm object!'); - } - - return rpc.callRealmMethod(realmId, name, Array.from(arguments)); - } - }); -}); +]); Object.defineProperty(Realm, 'Types', {value: types}); diff --git a/lib/results.js b/lib/results.js index 8154f6f6..1d709b1b 100644 --- a/lib/results.js +++ b/lib/results.js @@ -1,50 +1,30 @@ 'use strict'; +let internal = require('./internal-types'); +let keys = require('./keys'); let rpc = require('./rpc'); let util = require('./util'); -let idKey = util.idKey; -let realmKey = util.realmKey; -let resizeListKey = util.resizeListKey; -let prototype = {}; +module.exports = { + create, +}; -exports.create = create; +class Results {} -[ +util.createMethods(Results.prototype, internal.RESULTS, [ 'sortByProperty', -].forEach(function(name) { - Object.defineProperty(prototype, name, { - value: function() { - let resultsId = this[idKey]; - let realmId = this[realmKey]; - - if (!resultsId || !realmId) { - throw new TypeError(name + ' method was not called on Results!'); - } - - return rpc.callListMethod(realmId, resultsId, name, Array.from(arguments)); - } - }); -}); +]); function create(realmId, info) { - let results = util.createList(prototype, getterForLength, getterForIndex); - let size = info.size; - - results[realmKey] = realmId; - results[idKey] = info.id; - - results[resizeListKey](size); - - return results; + return util.createList(Results.prototype, realmId, info, getterForLength, getterForIndex); } function getterForLength() { - return rpc.getResultsSize(this[realmKey], this[idKey]); + return rpc.getResultsSize(this[keys.realm], this[keys.id]); } function getterForIndex(index) { return function() { - return rpc.getResultsItem(this[realmKey], this[idKey], index); + return rpc.getResultsItem(this[keys.realm], this[keys.id], index); }; } diff --git a/lib/rpc.js b/lib/rpc.js index d567dc42..5a91f40b 100644 --- a/lib/rpc.js +++ b/lib/rpc.js @@ -1,11 +1,10 @@ 'use strict'; -let util = require('./util'); +let internal = require('./internal-types'); +let keys = require('./keys'); let DEVICE_HOST = 'localhost:8082'; -let idKey = util.idKey; -let realmKey = util.realmKey; let typeConverters = {}; let XMLHttpRequest = window.XMLHttpRequest; @@ -21,7 +20,7 @@ module.exports = { registerTypeConverter, createRealm, - callRealmMethod, + callMethod, getObjectProperty, setObjectProperty, @@ -29,11 +28,9 @@ module.exports = { getListItem, setListItem, getListSize, - callListMethod, getResultsItem, getResultsSize, - callResultsMethod, beginTransaction, cancelTransaction, @@ -54,63 +51,46 @@ function createRealm(args) { return sendRequest('create_realm', {arguments: args}); } -function callRealmMethod(realmId, name, args) { +function callMethod(realmId, id, type, name, args) { if (args) { args = args.map((arg) => serialize(realmId, arg)); } - let result = sendRequest('call_realm_method', {realmId, name, arguments: args}); + let result = sendRequest('call_method', {realmId, id, type, name, arguments: args}); return deserialize(realmId, result); } -function getObjectProperty(realmId, objectId, name) { - let result = sendRequest('get_property', {realmId, objectId, name}); +function getObjectProperty(realmId, id, name) { + let result = sendRequest('get_property', {realmId, id, name}); return deserialize(realmId, result); } -function setObjectProperty(realmId, objectId, name, value) { +function setObjectProperty(realmId, id, name, value) { value = serialize(realmId, value); - sendRequest('set_property', {realmId, objectId, name, value}); + sendRequest('set_property', {realmId, id, name, value}); } -function getListItem(realmId, listId, index) { - let result = sendRequest('get_list_item', {realmId, listId, index}); +function getListItem(realmId, id, index) { + let result = sendRequest('get_list_item', {realmId, id, index}); return deserialize(realmId, result); } -function setListItem(realmId, listId, index, value) { - sendRequest('set_list_item', {realmId, listId, index, value: serialize(realmId, value)}); +function setListItem(realmId, id, index, value) { + value = serialize(realmId, value); + sendRequest('set_list_item', {realmId, id, index, value}); } -function getListSize(realmId, listId) { - return sendRequest('get_list_size', {realmId, listId}); +function getListSize(realmId, id) { + return sendRequest('get_list_size', {realmId, id}); } -function callListMethod(realmId, listId, name, args) { - if (args) { - args = args.map((arg) => serialize(realmId, arg)); - } - - let result = sendRequest('call_list_method', {realmId, listId, name, arguments: args}); +function getResultsItem(realmId, id, index) { + let result = sendRequest('get_results_item', {realmId, id, index}); return deserialize(realmId, result); } -function getResultsItem(realmId, resultsId, index) { - let result = sendRequest('get_results_item', {realmId, resultsId, index}); - return deserialize(realmId, result); -} - -function getResultsSize(realmId, resultsId) { - return sendRequest('get_results_size', {realmId, resultsId}); -} - -function callResultsMethod(realmId, resultsId, name, args) { - if (args) { - args = args.map((arg) => serialize(realmId, arg)); - } - - let result = sendRequest('call_results_method', {realmId, resultsId, name, arguments: args}); - return deserialize(realmId, result); +function getResultsSize(realmId, id) { + return sendRequest('get_results_size', {realmId, id}); } function beginTransaction(realmId) { @@ -131,16 +111,16 @@ function clearTestState() { function serialize(realmId, value) { if (typeof value == 'function') { - return {type: 'ObjectTypesFUNCTION'}; + return {type: internal.FUNCTION}; } if (!value || typeof value != 'object') { return {value: value}; } - let id = value[idKey]; + let id = value[keys.id]; if (id) { - if (value[realmKey] != realmId) { + if (value[keys.realm] != realmId) { throw new Error('Unable to serialize value from another Realm'); } diff --git a/lib/types.js b/lib/types.js index 17fd5f28..9552c8b1 100644 --- a/lib/types.js +++ b/lib/types.js @@ -13,7 +13,10 @@ let types = {}; 'OBJECT', 'LIST', ].forEach(function(type) { - types[type] = 'PropTypes' + type; + Object.defineProperty(types, type, { + value: 'PropTypes' + type, + enumerable: true, + }); }); module.exports = Object.freeze(types); diff --git a/lib/util.js b/lib/util.js index 51f21445..c90c770a 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1,18 +1,20 @@ 'use strict'; -let idKey = exports.idKey = Symbol(); -let realmKey = exports.realmKey = Symbol(); -let resizeListKey = exports.resizeListKey = Symbol(); +let keys = require('./keys'); +let rpc = require('./rpc'); -exports.createList = createList; +module.exports = { + createList, + createMethods, +}; -function createList(prototype, getterForLength, getterForIndex, setterForIndex) { - let list = prototype ? Object.create(prototype) : {}; +function createList(prototype, realmId, info, getterForLength, getterForIndex, setterForIndex) { + let list = Object.create(prototype); let size = 0; Object.defineProperty(list, 'length', {get: getterForLength}); - list[resizeListKey] = function(length) { + list[keys.resize] = function(length) { if (length == null) { length = this.length; } @@ -43,5 +45,40 @@ function createList(prototype, getterForLength, getterForIndex, setterForIndex) size = length; }; + list[keys.realm] = realmId; + list[keys.id] = info.id; + list[keys.type] = info.type; + list[keys.resize](info.size); + return list; } + +function createMethods(prototype, type, methodNames, resize) { + let props = {}; + + for (let name of methodNames) { + props[name] = { + value: function() { + let realmId = this[keys.realm]; + let id = this[keys.id]; + + if (!realmId || !id) { + throw new TypeError(name + ' method was not called a Realm object!'); + } + if (this[keys.type] !== type) { + throw new TypeError(name + ' method was called on an object of the wrong type!'); + } + + let result = rpc.callMethod(realmId, id, type, name, Array.from(arguments)); + + if (resize) { + this[keys.resize](); + } + + return result; + } + }; + } + + Object.defineProperties(prototype, props); +} diff --git a/src/RealmRPC.mm b/src/RealmRPC.mm index 53161a33..ebf95c59 100644 --- a/src/RealmRPC.mm +++ b/src/RealmRPC.mm @@ -35,12 +35,25 @@ using RPCObjectID = u_int64_t; using RPCRequest = std::function; +static const char * const RealmObjectTypesFunction = "ObjectTypesFUNCTION"; +static const char * const RealmObjectTypesNotification = "ObjectTypesNOTIFICATION"; +static const char * const RealmObjectTypesRealm = "ObjectTypesREALM"; +static const char * const RealmObjectTypesResults = "ObjectTypesRESULTS"; + +static std::map s_objectTypeMethods; + @implementation RJSRPCServer { JSGlobalContextRef _context; std::map _requests; std::map _objects; } ++ (void)initialize { + s_objectTypeMethods[RealmObjectTypesRealm] = RJSRealmFuncs; + s_objectTypeMethods[RealmObjectTypesResults] = RJSResultsFuncs; + s_objectTypeMethods[RJSTypeGet(realm::PropertyTypeArray)] = RJSListFuncs; +} + - (void)dealloc { for (auto item : _objects) { JSValueUnprotect(_context, item.second); @@ -92,18 +105,17 @@ using RPCRequest = std::function; RJSGetInternal(_objects[realmId])->get()->commit_transaction(); return nil; }; - _requests["/call_realm_method"] = [=](NSDictionary *dict) { - NSString *name = dict[@"name"]; - return [self performObjectMethod:name.UTF8String - classMethods:RJSRealmFuncs + _requests["/call_method"] = [=](NSDictionary *dict) { + return [self performObjectMethod:dict[@"name"] + withType:dict[@"type"] args:dict[@"arguments"] - objectId:[dict[@"realmId"] unsignedLongValue]]; + objectId:[dict[@"id"] unsignedLongValue]]; }; _requests["/get_property"] = [=](NSDictionary *dict) { JSValueRef exception = NULL; NSString *name = dict[@"name"]; JSStringRef propString = RJSStringForString(name.UTF8String); - RPCObjectID objectId = [dict[@"objectId"] unsignedLongValue]; + RPCObjectID objectId = [dict[@"id"] unsignedLongValue]; JSValueRef propertyValue = ObjectGetProperty(_context, _objects[objectId], propString, &exception); JSStringRelease(propString); @@ -114,11 +126,11 @@ using RPCRequest = std::function; }; _requests["/set_property"] = [=](NSDictionary *dict) { JSStringRef propString = RJSStringForString([dict[@"name"] UTF8String]); - RPCObjectID realmId = [dict[@"objectId"] unsignedLongValue]; + RPCObjectID objectId = [dict[@"id"] unsignedLongValue]; JSValueRef value = [self deserializeDictionaryValue:dict[@"value"]]; JSValueRef exception = NULL; - ObjectSetProperty(_context, _objects[realmId], propString, value, &exception); + ObjectSetProperty(_context, _objects[objectId], propString, value, &exception); JSStringRelease(propString); return exception ? @{@"error": @(RJSStringForValue(_context, exception).c_str())} : @{}; @@ -130,7 +142,7 @@ using RPCRequest = std::function; return nil; }; _requests["/get_results_size"] = [=](NSDictionary *dict) { - RPCObjectID resultsId = [dict[@"resultsId"] unsignedLongValue]; + RPCObjectID resultsId = [dict[@"id"] unsignedLongValue]; JSValueRef exception = NULL; static JSStringRef lengthPropertyName = JSStringCreateWithUTF8CString("length"); @@ -143,7 +155,7 @@ using RPCRequest = std::function; return @{@"result": @(length)}; }; _requests["/get_results_item"] = [=](NSDictionary *dict) { - RPCObjectID resultsId = [dict[@"resultsId"] unsignedLongValue]; + RPCObjectID resultsId = [dict[@"id"] unsignedLongValue]; long index = [dict[@"index"] longValue]; JSValueRef exception = NULL; @@ -157,7 +169,7 @@ using RPCRequest = std::function; return @{@"result": [self resultForJSValue:objectValue]}; }; _requests["/get_list_size"] = [=](NSDictionary *dict) { - RPCObjectID listId = [dict[@"listId"] unsignedLongValue]; + RPCObjectID listId = [dict[@"id"] unsignedLongValue]; JSValueRef exception = NULL; static JSStringRef lengthPropertyName = JSStringCreateWithUTF8CString("length"); @@ -169,15 +181,8 @@ using RPCRequest = std::function; } return @{@"result": @(length)}; }; - _requests["/call_results_method"] = [=](NSDictionary *dict) { - NSString *name = dict[@"name"]; - return [self performObjectMethod:name.UTF8String - classMethods:RJSResultsFuncs - args:dict[@"arguments"] - objectId:[dict[@"resultsId"] unsignedLongValue]]; - }; _requests["/get_list_item"] = [=](NSDictionary *dict) { - RPCObjectID listId = [dict[@"listId"] unsignedLongValue]; + RPCObjectID listId = [dict[@"id"] unsignedLongValue]; long index = [dict[@"index"] longValue]; JSValueRef exception = NULL; @@ -188,11 +193,10 @@ using RPCRequest = std::function; if (exception) { return @{@"error": @(RJSStringForValue(_context, exception).c_str())}; } - return @{@"result": [self resultForJSValue:objectValue]}; }; _requests["/set_list_item"] = [=](NSDictionary *dict) { - RPCObjectID listId = [dict[@"listId"] unsignedLongValue]; + RPCObjectID listId = [dict[@"id"] unsignedLongValue]; long index = [dict[@"index"] longValue]; JSValueRef exception = NULL; @@ -204,16 +208,8 @@ using RPCRequest = std::function; if (exception) { return @{@"error": @(RJSStringForValue(_context, exception).c_str())}; } - return @{}; }; - _requests["/call_list_method"] = [=](NSDictionary *dict) { - NSString *name = dict[@"name"]; - return [self performObjectMethod:name.UTF8String - classMethods:RJSListFuncs - args:dict[@"arguments"] - objectId:[dict[@"listId"] unsignedLongValue]]; - }; _requests["/clear_test_state"] = [=](NSDictionary *dict) { for (auto object : _objects) { JSValueUnprotect(_context, object.second); @@ -243,16 +239,18 @@ using RPCRequest = std::function; return response ?: @{}; } -- (NSDictionary *)performObjectMethod:(const char *)name - classMethods:(const JSStaticFunction [])methods +- (NSDictionary *)performObjectMethod:(NSString *)method + withType:(NSString *)type args:(NSArray *)args objectId:(RPCObjectID)oid { + const JSStaticFunction *methods = s_objectTypeMethods.at(type.UTF8String); NSUInteger argCount = args.count; JSValueRef argValues[argCount]; for (NSUInteger i = 0; i < argCount; i++) { argValues[i] = [self deserializeDictionaryValue:args[i]]; } + const char *name = method.UTF8String; size_t index = 0; while (methods[index].name) { if (!strcmp(methods[index].name, name)) { @@ -266,7 +264,7 @@ using RPCRequest = std::function; index++; } - return @{@"error": @"invalid method"}; + return @{@"error": [NSString stringWithFormat:@"Attempted to call invalid method (%@) on type: %@", method, type]}; } - (RPCObjectID)storeObject:(JSObjectRef)object { @@ -318,7 +316,7 @@ using RPCRequest = std::function; realm::Results *results = RJSGetInternal(jsObject); RPCObjectID oid = [self storeObject:jsObject]; return @{ - @"type": @"ObjectTypesRESULTS", + @"type": @(RealmObjectTypesResults), @"id": @(oid), @"size": @(results->size()), @"schema": [self objectSchemaToJSONObject:results->object_schema] @@ -327,7 +325,7 @@ using RPCRequest = std::function; else if (JSValueIsObjectOfClass(_context, value, RJSNotificationClass())) { RPCObjectID oid = [self storeObject:jsObject]; return @{ - @"type": @"ObjectTypesNOTIFICATION", + @"type": @(RealmObjectTypesNotification), @"id": @(oid), }; } @@ -369,7 +367,7 @@ using RPCRequest = std::function; } NSString *type = dict[@"type"]; - if ([type isEqualToString:@"ObjectTypesFUNCTION"]) { + if ([type isEqualToString:@(RealmObjectTypesFunction)]) { // 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); From 1b7653206a97eb79bd25702e9c0b09b476897ae0 Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Mon, 19 Oct 2015 15:26:42 -0700 Subject: [PATCH 07/15] Generalize getting/setting properties through RPC --- lib/lists.js | 20 +------- lib/objects.js | 18 ++----- lib/results.js | 14 +----- lib/rpc.js | 40 ++-------------- lib/util.js | 22 +++++++-- src/RJSList.hpp | 3 -- src/RJSObject.hpp | 4 -- src/RJSResults.hpp | 3 -- src/RealmRPC.mm | 115 ++++++++++++++------------------------------- 9 files changed, 62 insertions(+), 177 deletions(-) diff --git a/lib/lists.js b/lib/lists.js index 536d9e8c..8c207ab8 100644 --- a/lib/lists.js +++ b/lib/lists.js @@ -1,7 +1,5 @@ 'use strict'; -let keys = require('./keys'); -let rpc = require('./rpc'); let types = require('./types'); let util = require('./util'); @@ -20,21 +18,5 @@ util.createMethods(List.prototype, types.LIST, [ ], true); function create(realmId, info) { - return util.createList(List.prototype, realmId, info, getterForLength, getterForIndex, setterForIndex); -} - -function getterForLength() { - return rpc.getListSize(this[keys.realm], this[keys.id]); -} - -function getterForIndex(index) { - return function() { - return rpc.getListItem(this[keys.realm], this[keys.id], index); - }; -} - -function setterForIndex(index) { - return function(value) { - rpc.setListItem(this[keys.realm], this[keys.id], index, value); - }; + return util.createList(List.prototype, realmId, info, true); } diff --git a/lib/objects.js b/lib/objects.js index c5737393..266e0fd5 100644 --- a/lib/objects.js +++ b/lib/objects.js @@ -1,7 +1,7 @@ 'use strict'; let keys = require('./keys'); -let rpc = require('./rpc'); +let util = require('./util'); let registeredConstructors = {}; @@ -24,8 +24,8 @@ function create(realmId, info) { let name = prop.name; props[name] = { - get: getterForProperty(name), - set: setterForProperty(name), + get: util.getterForProperty(name), + set: util.setterForProperty(name), }; } @@ -37,15 +37,3 @@ function create(realmId, info) { function registerConstructors(realmId, constructors) { registeredConstructors[realmId] = constructors; } - -function getterForProperty(name) { - return function() { - return rpc.getObjectProperty(this[keys.realm], this[keys.id], name); - }; -} - -function setterForProperty(name) { - return function(value) { - rpc.setObjectProperty(this[keys.realm], this[keys.id], name, value); - }; -} diff --git a/lib/results.js b/lib/results.js index 1d709b1b..96062d02 100644 --- a/lib/results.js +++ b/lib/results.js @@ -1,8 +1,6 @@ 'use strict'; let internal = require('./internal-types'); -let keys = require('./keys'); -let rpc = require('./rpc'); let util = require('./util'); module.exports = { @@ -16,15 +14,5 @@ util.createMethods(Results.prototype, internal.RESULTS, [ ]); function create(realmId, info) { - return util.createList(Results.prototype, realmId, info, getterForLength, getterForIndex); -} - -function getterForLength() { - return rpc.getResultsSize(this[keys.realm], this[keys.id]); -} - -function getterForIndex(index) { - return function() { - return rpc.getResultsItem(this[keys.realm], this[keys.id], index); - }; + return util.createList(Results.prototype, realmId, info); } diff --git a/lib/rpc.js b/lib/rpc.js index 5a91f40b..f04d9c04 100644 --- a/lib/rpc.js +++ b/lib/rpc.js @@ -21,17 +21,8 @@ module.exports = { createRealm, callMethod, - - getObjectProperty, - setObjectProperty, - - getListItem, - setListItem, - getListSize, - - getResultsItem, - getResultsSize, - + getProperty, + setProperty, beginTransaction, cancelTransaction, commitTransaction, @@ -60,39 +51,16 @@ function callMethod(realmId, id, type, name, args) { return deserialize(realmId, result); } -function getObjectProperty(realmId, id, name) { +function getProperty(realmId, id, name) { let result = sendRequest('get_property', {realmId, id, name}); return deserialize(realmId, result); } -function setObjectProperty(realmId, id, name, value) { +function setProperty(realmId, id, name, value) { value = serialize(realmId, value); sendRequest('set_property', {realmId, id, name, value}); } -function getListItem(realmId, id, index) { - let result = sendRequest('get_list_item', {realmId, id, index}); - return deserialize(realmId, result); -} - -function setListItem(realmId, id, index, value) { - value = serialize(realmId, value); - sendRequest('set_list_item', {realmId, id, index, value}); -} - -function getListSize(realmId, id) { - return sendRequest('get_list_size', {realmId, id}); -} - -function getResultsItem(realmId, id, index) { - let result = sendRequest('get_results_item', {realmId, id, index}); - return deserialize(realmId, result); -} - -function getResultsSize(realmId, id) { - return sendRequest('get_results_size', {realmId, id}); -} - function beginTransaction(realmId) { sendRequest('begin_transaction', {realmId}); } diff --git a/lib/util.js b/lib/util.js index c90c770a..72c617f7 100644 --- a/lib/util.js +++ b/lib/util.js @@ -6,13 +6,15 @@ let rpc = require('./rpc'); module.exports = { createList, createMethods, + getterForProperty, + setterForProperty, }; -function createList(prototype, realmId, info, getterForLength, getterForIndex, setterForIndex) { +function createList(prototype, realmId, info, mutable) { let list = Object.create(prototype); let size = 0; - Object.defineProperty(list, 'length', {get: getterForLength}); + Object.defineProperty(list, 'length', {get: getterForProperty('length')}); list[keys.resize] = function(length) { if (length == null) { @@ -27,8 +29,8 @@ function createList(prototype, realmId, info, getterForLength, getterForIndex, s for (let i = size; i < length; i++) { props[i] = { - get: getterForIndex(i), - set: setterForIndex && setterForIndex(i), + get: getterForProperty(i), + set: mutable ? setterForProperty(i) : undefined, enumerable: true, configurable: true, }; @@ -82,3 +84,15 @@ function createMethods(prototype, type, methodNames, resize) { Object.defineProperties(prototype, props); } + +function getterForProperty(name) { + return function() { + return rpc.getProperty(this[keys.realm], this[keys.id], name); + }; +} + +function setterForProperty(name) { + return function(value) { + rpc.setProperty(this[keys.realm], this[keys.id], name, value); + }; +} diff --git a/src/RJSList.hpp b/src/RJSList.hpp index aaa07571..03666854 100644 --- a/src/RJSList.hpp +++ b/src/RJSList.hpp @@ -23,6 +23,3 @@ extern const JSStaticFunction RJSListFuncs[]; JSClassRef RJSListClass(); JSObjectRef RJSListCreate(JSContextRef ctx, realm::List &list); - -JSValueRef ListGetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* jsException); -bool ListSetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* jsException); \ No newline at end of file diff --git a/src/RJSObject.hpp b/src/RJSObject.hpp index 3ad62ed1..12ea06b9 100644 --- a/src/RJSObject.hpp +++ b/src/RJSObject.hpp @@ -24,7 +24,3 @@ namespace realm { JSClassRef RJSObjectClass(); JSObjectRef RJSObjectCreate(JSContextRef ctx, realm::Object object); - -JSValueRef ObjectGetProperty(JSContextRef ctx, JSObjectRef jsObject, JSStringRef jsPropertyName, JSValueRef* exception); -bool ObjectSetProperty(JSContextRef ctx, JSObjectRef jsObject, JSStringRef jsPropertyName, JSValueRef value, JSValueRef* exception); - diff --git a/src/RJSResults.hpp b/src/RJSResults.hpp index 62996604..09442d75 100644 --- a/src/RJSResults.hpp +++ b/src/RJSResults.hpp @@ -27,6 +27,3 @@ extern const JSStaticFunction RJSResultsFuncs[]; JSClassRef RJSResultsClass(); JSObjectRef RJSResultsCreate(JSContextRef ctx, realm::SharedRealm realm, std::string className); JSObjectRef RJSResultsCreate(JSContextRef ctx, realm::SharedRealm realm, std::string className, std::string query); - -JSValueRef ResultsGetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* jsException); - diff --git a/src/RealmRPC.mm b/src/RealmRPC.mm index ebf95c59..ce0e1276 100644 --- a/src/RealmRPC.mm +++ b/src/RealmRPC.mm @@ -19,6 +19,7 @@ #import "RealmRPC.h" #import +#include #include #include #include "RealmJS.h" @@ -68,6 +69,13 @@ static std::map s_objectTypeMethods; if (self) { _context = JSGlobalContextCreate(NULL); + // 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); + } + id _self = self; __weak __typeof__(self) self = _self; @@ -112,28 +120,44 @@ static std::map s_objectTypeMethods; objectId:[dict[@"id"] unsignedLongValue]]; }; _requests["/get_property"] = [=](NSDictionary *dict) { + RPCObjectID oid = [dict[@"id"] unsignedLongValue]; + id name = dict[@"name"]; + JSValueRef value = NULL; JSValueRef exception = NULL; - NSString *name = dict[@"name"]; - JSStringRef propString = RJSStringForString(name.UTF8String); - RPCObjectID objectId = [dict[@"id"] unsignedLongValue]; - JSValueRef propertyValue = ObjectGetProperty(_context, _objects[objectId], propString, &exception); - JSStringRelease(propString); + + if ([name isKindOfClass:[NSNumber class]]) { + value = JSObjectGetPropertyAtIndex(_context, _objects[oid], [name unsignedIntValue], &exception); + } + else { + JSStringRef propString = RJSStringForString([name UTF8String]); + value = JSObjectGetProperty(_context, _objects[oid], propString, &exception); + JSStringRelease(propString); + } if (exception) { return @{@"error": @(RJSStringForValue(_context, exception).c_str())}; } - return @{@"result": [self resultForJSValue:propertyValue]}; + return @{@"result": [self resultForJSValue:value]}; }; _requests["/set_property"] = [=](NSDictionary *dict) { - JSStringRef propString = RJSStringForString([dict[@"name"] UTF8String]); - RPCObjectID objectId = [dict[@"id"] unsignedLongValue]; + RPCObjectID oid = [dict[@"id"] unsignedLongValue]; + id name = dict[@"name"]; JSValueRef value = [self deserializeDictionaryValue:dict[@"value"]]; JSValueRef exception = NULL; - ObjectSetProperty(_context, _objects[objectId], propString, value, &exception); - JSStringRelease(propString); + if ([name isKindOfClass:[NSNumber class]]) { + JSObjectSetPropertyAtIndex(_context, _objects[oid], [name unsignedIntValue], value, &exception); + } + else { + JSStringRef propString = RJSStringForString([name UTF8String]); + JSObjectSetProperty(_context, _objects[oid], propString, value, 0, &exception); + JSStringRelease(propString); + } - return exception ? @{@"error": @(RJSStringForValue(_context, exception).c_str())} : @{}; + if (exception) { + return @{@"error": @(RJSStringForValue(_context, exception).c_str())}; + } + return @{}; }; _requests["/dispose_object"] = [=](NSDictionary *dict) { RPCObjectID oid = [dict[@"id"] unsignedLongValue]; @@ -141,75 +165,6 @@ static std::map s_objectTypeMethods; _objects.erase(oid); return nil; }; - _requests["/get_results_size"] = [=](NSDictionary *dict) { - RPCObjectID resultsId = [dict[@"id"] unsignedLongValue]; - - JSValueRef exception = NULL; - static JSStringRef lengthPropertyName = JSStringCreateWithUTF8CString("length"); - 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)}; - }; - _requests["/get_results_item"] = [=](NSDictionary *dict) { - RPCObjectID resultsId = [dict[@"id"] unsignedLongValue]; - long index = [dict[@"index"] longValue]; - - JSValueRef exception = NULL; - JSStringRef indexPropertyName = JSStringCreateWithUTF8CString(std::to_string(index).c_str()); - JSValueRef objectValue = ResultsGetProperty(_context, _objects[resultsId], indexPropertyName, &exception); - JSStringRelease(indexPropertyName); - - if (exception) { - return @{@"error": @(RJSStringForValue(_context, exception).c_str())}; - } - return @{@"result": [self resultForJSValue:objectValue]}; - }; - _requests["/get_list_size"] = [=](NSDictionary *dict) { - RPCObjectID listId = [dict[@"id"] unsignedLongValue]; - - JSValueRef exception = NULL; - static JSStringRef lengthPropertyName = JSStringCreateWithUTF8CString("length"); - JSValueRef lengthValue = ListGetProperty(_context, _objects[listId], lengthPropertyName, &exception); - size_t length = JSValueToNumber(_context, lengthValue, &exception); - - if (exception) { - return @{@"error": @(RJSStringForValue(_context, exception).c_str())}; - } - return @{@"result": @(length)}; - }; - _requests["/get_list_item"] = [=](NSDictionary *dict) { - RPCObjectID listId = [dict[@"id"] unsignedLongValue]; - long index = [dict[@"index"] longValue]; - - JSValueRef exception = NULL; - JSStringRef indexPropertyName = JSStringCreateWithUTF8CString(std::to_string(index).c_str()); - JSValueRef objectValue = ListGetProperty(_context, _objects[listId], indexPropertyName, &exception); - JSStringRelease(indexPropertyName); - - if (exception) { - return @{@"error": @(RJSStringForValue(_context, exception).c_str())}; - } - return @{@"result": [self resultForJSValue:objectValue]}; - }; - _requests["/set_list_item"] = [=](NSDictionary *dict) { - RPCObjectID listId = [dict[@"id"] unsignedLongValue]; - long index = [dict[@"index"] longValue]; - - JSValueRef exception = NULL; - JSStringRef indexPropertyName = JSStringCreateWithUTF8CString(std::to_string(index).c_str()); - JSValueRef value = [self deserializeDictionaryValue:dict[@"value"]]; - ListSetProperty(_context, _objects[listId], indexPropertyName, value, &exception); - JSStringRelease(indexPropertyName); - - if (exception) { - return @{@"error": @(RJSStringForValue(_context, exception).c_str())}; - } - return @{}; - }; _requests["/clear_test_state"] = [=](NSDictionary *dict) { for (auto object : _objects) { JSValueUnprotect(_context, object.second); From d172b43535e315f1c3b6e4faa3183e0afc3651cb Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Mon, 19 Oct 2015 15:46:28 -0700 Subject: [PATCH 08/15] Add path and schemaVersion getters to realm object --- lib/realm.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/realm.js b/lib/realm.js index 6f9fb182..7e2f48d4 100644 --- a/lib/realm.js +++ b/lib/realm.js @@ -43,6 +43,13 @@ class Realm { this[keys.realm] = realmId; this[keys.type] = internal.REALM; this[notificationsKey] = []; + + [ + 'path', + 'schemaVersion', + ].forEach((name) => { + Object.defineProperty(this, name, {get: util.getterForProperty(name)}); + }); } addNotification(callback) { From 57778ce878bba79f3c613376f2691c9e601eb8f4 Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Mon, 19 Oct 2015 15:52:32 -0700 Subject: [PATCH 09/15] Improve calling object methods through RPC --- lib/realm.js | 2 +- lib/rpc.js | 4 ++-- lib/util.js | 2 +- src/RJSList.cpp | 2 +- src/RJSList.hpp | 1 - src/RJSRealm.hpp | 2 -- src/RJSRealm.mm | 2 +- src/RJSResults.hpp | 1 - src/RJSResults.mm | 2 +- src/RealmRPC.mm | 41 ++++++++++++----------------------------- 10 files changed, 19 insertions(+), 40 deletions(-) diff --git a/lib/realm.js b/lib/realm.js index 7e2f48d4..55ddbf46 100644 --- a/lib/realm.js +++ b/lib/realm.js @@ -62,7 +62,7 @@ class Realm { throw new Error('Realm.addNotification must be passed a function!'); } - let notification = rpc.callMethod(realmId, realmId, internal.REALM, 'addNotification', [callback]); + let notification = rpc.callMethod(realmId, realmId, 'addNotification', [callback]); notification[notificationCallbackKey] = callback; this[notificationsKey].push(notification); diff --git a/lib/rpc.js b/lib/rpc.js index f04d9c04..2e7508f4 100644 --- a/lib/rpc.js +++ b/lib/rpc.js @@ -42,12 +42,12 @@ function createRealm(args) { return sendRequest('create_realm', {arguments: args}); } -function callMethod(realmId, id, type, name, args) { +function callMethod(realmId, id, name, args) { if (args) { args = args.map((arg) => serialize(realmId, arg)); } - let result = sendRequest('call_method', {realmId, id, type, name, arguments: args}); + let result = sendRequest('call_method', {realmId, id, name, arguments: args}); return deserialize(realmId, result); } diff --git a/lib/util.js b/lib/util.js index 72c617f7..c6ab34b9 100644 --- a/lib/util.js +++ b/lib/util.js @@ -71,7 +71,7 @@ function createMethods(prototype, type, methodNames, resize) { throw new TypeError(name + ' method was called on an object of the wrong type!'); } - let result = rpc.callMethod(realmId, id, type, name, Array.from(arguments)); + let result = rpc.callMethod(realmId, id, name, Array.from(arguments)); if (resize) { this[keys.resize](); diff --git a/src/RJSList.cpp b/src/RJSList.cpp index 66978780..83179568 100644 --- a/src/RJSList.cpp +++ b/src/RJSList.cpp @@ -214,7 +214,7 @@ JSObjectRef RJSListCreate(JSContextRef ctx, realm::List &list) { return RJSWrapObject(ctx, RJSListClass(), new List(list)); } -const JSStaticFunction RJSListFuncs[] = { +static const JSStaticFunction RJSListFuncs[] = { {"push", ListPush, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, {"pop", ListPop, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, {"shift", ListShift, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, diff --git a/src/RJSList.hpp b/src/RJSList.hpp index 03666854..c820d21d 100644 --- a/src/RJSList.hpp +++ b/src/RJSList.hpp @@ -20,6 +20,5 @@ #import "shared_realm.hpp" #import "list.hpp" -extern const JSStaticFunction RJSListFuncs[]; JSClassRef RJSListClass(); JSObjectRef RJSListCreate(JSContextRef ctx, realm::List &list); diff --git a/src/RJSRealm.hpp b/src/RJSRealm.hpp index d2002ea1..edec1217 100644 --- a/src/RJSRealm.hpp +++ b/src/RJSRealm.hpp @@ -18,8 +18,6 @@ #import "RJSUtil.hpp" -extern const JSStaticFunction RJSRealmFuncs[]; - JSClassRef RJSRealmClass(); JSClassRef RJSRealmConstructorClass(); JSClassRef RJSNotificationClass(); diff --git a/src/RJSRealm.mm b/src/RJSRealm.mm index 78d45c5c..6fcc7c25 100644 --- a/src/RJSRealm.mm +++ b/src/RJSRealm.mm @@ -433,7 +433,7 @@ JSValueRef RealmClose(JSContextRef ctx, JSObjectRef function, JSObjectRef thisOb return NULL; } -const JSStaticFunction RJSRealmFuncs[] = { +static const JSStaticFunction RJSRealmFuncs[] = { {"objects", RealmObjects, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, {"create", RealmCreateObject, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, {"delete", RealmDelete, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, diff --git a/src/RJSResults.hpp b/src/RJSResults.hpp index 09442d75..b1ba3b26 100644 --- a/src/RJSResults.hpp +++ b/src/RJSResults.hpp @@ -23,7 +23,6 @@ namespace realm { typedef std::shared_ptr SharedRealm; } -extern const JSStaticFunction RJSResultsFuncs[]; JSClassRef RJSResultsClass(); JSObjectRef RJSResultsCreate(JSContextRef ctx, realm::SharedRealm realm, std::string className); JSObjectRef RJSResultsCreate(JSContextRef ctx, realm::SharedRealm realm, std::string className, std::string query); diff --git a/src/RJSResults.mm b/src/RJSResults.mm index 1ed15eab..661847b0 100644 --- a/src/RJSResults.mm +++ b/src/RJSResults.mm @@ -118,7 +118,7 @@ JSObjectRef RJSResultsCreate(JSContextRef ctx, SharedRealm realm, std::string cl return RJSWrapObject(ctx, RJSResultsClass(), new Results(realm, *object_schema, std::move(query))); } -const JSStaticFunction RJSResultsFuncs[] = { +static const JSStaticFunction RJSResultsFuncs[] = { {"sortByProperty", SortByProperty, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, {NULL, NULL}, }; diff --git a/src/RealmRPC.mm b/src/RealmRPC.mm index ce0e1276..1e914fd1 100644 --- a/src/RealmRPC.mm +++ b/src/RealmRPC.mm @@ -38,23 +38,14 @@ using RPCRequest = std::function; static const char * const RealmObjectTypesFunction = "ObjectTypesFUNCTION"; static const char * const RealmObjectTypesNotification = "ObjectTypesNOTIFICATION"; -static const char * const RealmObjectTypesRealm = "ObjectTypesREALM"; static const char * const RealmObjectTypesResults = "ObjectTypesRESULTS"; -static std::map s_objectTypeMethods; - @implementation RJSRPCServer { JSGlobalContextRef _context; std::map _requests; std::map _objects; } -+ (void)initialize { - s_objectTypeMethods[RealmObjectTypesRealm] = RJSRealmFuncs; - s_objectTypeMethods[RealmObjectTypesResults] = RJSResultsFuncs; - s_objectTypeMethods[RJSTypeGet(realm::PropertyTypeArray)] = RJSListFuncs; -} - - (void)dealloc { for (auto item : _objects) { JSValueUnprotect(_context, item.second); @@ -115,7 +106,6 @@ static std::map s_objectTypeMethods; }; _requests["/call_method"] = [=](NSDictionary *dict) { return [self performObjectMethod:dict[@"name"] - withType:dict[@"type"] args:dict[@"arguments"] objectId:[dict[@"id"] unsignedLongValue]]; }; @@ -194,32 +184,25 @@ static std::map s_objectTypeMethods; return response ?: @{}; } -- (NSDictionary *)performObjectMethod:(NSString *)method - withType:(NSString *)type - args:(NSArray *)args - objectId:(RPCObjectID)oid { - const JSStaticFunction *methods = s_objectTypeMethods.at(type.UTF8String); +- (NSDictionary *)performObjectMethod:(NSString *)method args:(NSArray *)args objectId:(RPCObjectID)oid { + JSObjectRef object = _objects[oid]; + JSStringRef methodString = RJSStringForString(method.UTF8String); + JSObjectRef function = RJSValidatedObjectProperty(_context, object, methodString); + JSStringRelease(methodString); + NSUInteger argCount = args.count; JSValueRef argValues[argCount]; for (NSUInteger i = 0; i < argCount; i++) { argValues[i] = [self deserializeDictionaryValue:args[i]]; } - const char *name = method.UTF8String; - size_t index = 0; - while (methods[index].name) { - if (!strcmp(methods[index].name, name)) { - JSValueRef exception = NULL; - JSValueRef ret = methods[index].callAsFunction(_context, NULL, _objects[oid], argCount, argValues, &exception); - if (exception) { - return @{@"error": @(RJSStringForValue(_context, exception).c_str())}; - } - return @{@"result": [self resultForJSValue:ret]}; - } - index++; - } + JSValueRef exception = NULL; + JSValueRef result = JSObjectCallAsFunction(_context, function, object, argCount, argValues, &exception); - return @{@"error": [NSString stringWithFormat:@"Attempted to call invalid method (%@) on type: %@", method, type]}; + if (exception) { + return @{@"error": @(RJSStringForValue(_context, exception).c_str())}; + } + return @{@"result": [self resultForJSValue:result]}; } - (RPCObjectID)storeObject:(JSObjectRef)object { From b07aa72a559074b617286675bc1c5a134decda40 Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Mon, 19 Oct 2015 16:19:43 -0700 Subject: [PATCH 10/15] Move all constants into a single JS file --- lib/constants.js | 48 +++++++++++++++++++++++++++++++++++++++++++ lib/internal-types.js | 16 --------------- lib/keys.js | 8 -------- lib/lists.js | 4 ++-- lib/notifications.js | 18 ++++++++-------- lib/objects.js | 3 ++- lib/realm.js | 23 +++++++++++---------- lib/results.js | 4 ++-- lib/rpc.js | 6 +++--- lib/types.js | 22 -------------------- lib/util.js | 4 +++- 11 files changed, 82 insertions(+), 74 deletions(-) create mode 100644 lib/constants.js delete mode 100644 lib/internal-types.js delete mode 100644 lib/keys.js delete mode 100644 lib/types.js diff --git a/lib/constants.js b/lib/constants.js new file mode 100644 index 00000000..813db4aa --- /dev/null +++ b/lib/constants.js @@ -0,0 +1,48 @@ +'use strict'; + +let keys = {}; +let objectTypes = {}; +let propTypes = {}; + +[ + 'id', + 'realm', + 'resize', + 'type', +].forEach(function(name) { + keys[name] = Symbol(); +}); + +[ + 'FUNCTION', + 'NOTIFICATION', + 'REALM', + 'RESULTS', +].forEach(function(type) { + Object.defineProperty(objectTypes, type, { + value: 'ObjectTypes' + type, + }); +}); + +[ + 'BOOL', + 'INT', + 'FLOAT', + 'DOUBLE', + 'STRING', + 'DATE', + 'DATA', + 'OBJECT', + 'LIST', +].forEach(function(type) { + Object.defineProperty(propTypes, type, { + value: 'PropTypes' + type, + enumerable: true, + }); +}); + +module.exports = { + keys, + objectTypes, + propTypes +}; diff --git a/lib/internal-types.js b/lib/internal-types.js deleted file mode 100644 index 0d494a5a..00000000 --- a/lib/internal-types.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict'; - -let types = {}; - -[ - 'FUNCTION', - 'NOTIFICATION', - 'REALM', - 'RESULTS', -].forEach(function(type) { - Object.defineProperty(types, type, { - value: 'ObjectTypes' + type, - }); -}); - -module.exports = Object.freeze(types); diff --git a/lib/keys.js b/lib/keys.js deleted file mode 100644 index c33fd42f..00000000 --- a/lib/keys.js +++ /dev/null @@ -1,8 +0,0 @@ -'use strict'; - -module.exports = Object.freeze({ - id: Symbol(), - realm: Symbol(), - resize: Symbol(), - type: Symbol(), -}); diff --git a/lib/lists.js b/lib/lists.js index 8c207ab8..5ee8a6f7 100644 --- a/lib/lists.js +++ b/lib/lists.js @@ -1,6 +1,6 @@ 'use strict'; -let types = require('./types'); +let constants = require('./constants'); let util = require('./util'); module.exports = { @@ -9,7 +9,7 @@ module.exports = { class List {} -util.createMethods(List.prototype, types.LIST, [ +util.createMethods(List.prototype, constants.propTypes.LIST, [ 'pop', 'shift', 'push', diff --git a/lib/notifications.js b/lib/notifications.js index cdd48f54..031ed7ae 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -1,19 +1,21 @@ 'use strict'; -let util = require('./util'); +let constants = require('./constants'); -let idKey = util.idKey; -let realmKey = util.realmKey; +let {keys} = constants; -exports.create = create; +module.exports = { + create, +}; + +class Notification {} function create(realmId, info) { let notification = new Notification(); - notification[realmKey] = realmId; - notification[idKey] = info.id; + notification[keys.realm] = realmId; + notification[keys.id] = info.id; + notification[keys.type] = info.type; return notification; } - -class Notification {} diff --git a/lib/objects.js b/lib/objects.js index 266e0fd5..00705f41 100644 --- a/lib/objects.js +++ b/lib/objects.js @@ -1,8 +1,9 @@ 'use strict'; -let keys = require('./keys'); +let constants = require('./constants'); let util = require('./util'); +let {keys} = constants; let registeredConstructors = {}; module.exports = { diff --git a/lib/realm.js b/lib/realm.js index 55ddbf46..b6101efe 100644 --- a/lib/realm.js +++ b/lib/realm.js @@ -1,24 +1,23 @@ 'use strict'; -let internal = require('./internal-types'); -let keys = require('./keys'); +let constants = require('./constants'); let lists = require('./lists'); let objects = require('./objects'); let notifications = require('./notifications'); let results = require('./results'); let rpc = require('./rpc'); -let types = require('./types'); let util = require('./util'); +let {keys, propTypes, objectTypes} = constants; let notificationsKey = Symbol(); let notificationCallbackKey = Symbol(); // TODO: DATA -rpc.registerTypeConverter(types.DATE, (_, info) => new Date(info.value)); -rpc.registerTypeConverter(types.LIST, lists.create); -rpc.registerTypeConverter(types.OBJECT, objects.create); -rpc.registerTypeConverter(internal.NOTIFICATION, notifications.create); -rpc.registerTypeConverter(internal.RESULTS, results.create); +rpc.registerTypeConverter(propTypes.DATE, (_, info) => new Date(info.value)); +rpc.registerTypeConverter(propTypes.LIST, lists.create); +rpc.registerTypeConverter(propTypes.OBJECT, objects.create); +rpc.registerTypeConverter(objectTypes.NOTIFICATION, notifications.create); +rpc.registerTypeConverter(objectTypes.RESULTS, results.create); class Realm { constructor(config) { @@ -41,7 +40,7 @@ class Realm { this[keys.id] = realmId; this[keys.realm] = realmId; - this[keys.type] = internal.REALM; + this[keys.type] = objectTypes.REALM; this[notificationsKey] = []; [ @@ -96,7 +95,7 @@ class Realm { } } -util.createMethods(Realm.prototype, internal.REALM, [ +util.createMethods(Realm.prototype, objectTypes.REALM, [ 'close', 'create', 'delete', @@ -104,7 +103,9 @@ util.createMethods(Realm.prototype, internal.REALM, [ 'objects', ]); -Object.defineProperty(Realm, 'Types', {value: types}); +Object.defineProperty(Realm, 'Types', { + value: Object.freeze(propTypes), +}); Object.defineProperty(Realm, 'clearTestState', { value: function() { diff --git a/lib/results.js b/lib/results.js index 96062d02..c598ddc1 100644 --- a/lib/results.js +++ b/lib/results.js @@ -1,6 +1,6 @@ 'use strict'; -let internal = require('./internal-types'); +let constants = require('./constants'); let util = require('./util'); module.exports = { @@ -9,7 +9,7 @@ module.exports = { class Results {} -util.createMethods(Results.prototype, internal.RESULTS, [ +util.createMethods(Results.prototype, constants.objectTypes.RESULTS, [ 'sortByProperty', ]); diff --git a/lib/rpc.js b/lib/rpc.js index 2e7508f4..889afcd7 100644 --- a/lib/rpc.js +++ b/lib/rpc.js @@ -1,10 +1,10 @@ 'use strict'; -let internal = require('./internal-types'); -let keys = require('./keys'); +let constants = require('./constants'); let DEVICE_HOST = 'localhost:8082'; +let {keys, objectTypes} = constants; let typeConverters = {}; let XMLHttpRequest = window.XMLHttpRequest; @@ -79,7 +79,7 @@ function clearTestState() { function serialize(realmId, value) { if (typeof value == 'function') { - return {type: internal.FUNCTION}; + return {type: objectTypes.FUNCTION}; } if (!value || typeof value != 'object') { diff --git a/lib/types.js b/lib/types.js deleted file mode 100644 index 9552c8b1..00000000 --- a/lib/types.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; - -let types = {}; - -[ - 'BOOL', - 'INT', - 'FLOAT', - 'DOUBLE', - 'STRING', - 'DATE', - 'DATA', - 'OBJECT', - 'LIST', -].forEach(function(type) { - Object.defineProperty(types, type, { - value: 'PropTypes' + type, - enumerable: true, - }); -}); - -module.exports = Object.freeze(types); diff --git a/lib/util.js b/lib/util.js index c6ab34b9..ba14ede7 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1,8 +1,10 @@ 'use strict'; -let keys = require('./keys'); +let constants = require('./constants'); let rpc = require('./rpc'); +let {keys} = constants; + module.exports = { createList, createMethods, From 03751c35b4bfd2f33bd75fba0f94467a411cce6e Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Mon, 19 Oct 2015 16:46:56 -0700 Subject: [PATCH 11/15] Handle passing dates through RPC --- lib/rpc.js | 6 +++++- src/RJSObject.mm | 17 ++--------------- src/RJSUtil.hpp | 14 +++++++++++--- src/RJSUtil.mm | 4 ++++ src/RealmRPC.mm | 19 ++++++++++++++++++- 5 files changed, 40 insertions(+), 20 deletions(-) diff --git a/lib/rpc.js b/lib/rpc.js index 889afcd7..d3813fb5 100644 --- a/lib/rpc.js +++ b/lib/rpc.js @@ -4,7 +4,7 @@ let constants = require('./constants'); let DEVICE_HOST = 'localhost:8082'; -let {keys, objectTypes} = constants; +let {keys, objectTypes, propTypes} = constants; let typeConverters = {}; let XMLHttpRequest = window.XMLHttpRequest; @@ -95,6 +95,10 @@ function serialize(realmId, value) { return {id}; } + if (value instanceof Date) { + return {type: propTypes.DATE, value: value.getTime()}; + } + if (Array.isArray(value)) { let array = value.map((item) => serialize(realmId, item)); return {value: array}; diff --git a/src/RJSObject.mm b/src/RJSObject.mm index b56c2801..567ccc96 100644 --- a/src/RJSObject.mm +++ b/src/RJSObject.mm @@ -142,21 +142,8 @@ template<> JSValueRef RJSAccessor::from_string(JSContextRef ctx, StringData s) { } template<> DateTime RJSAccessor::to_datetime(JSContextRef ctx, JSValueRef &val) { - JSObjectRef object = RJSValidatedValueToObject(ctx, val, "Property must be a Date"); - - JSValueRef exception = NULL; - static JSStringRef utcString = JSStringCreateWithUTF8CString("getTime"); - JSObjectRef utcGetter = RJSValidatedObjectProperty(ctx, object, utcString); - - JSValueRef utcVal = JSObjectCallAsFunction(ctx, utcGetter, object, 0, NULL, &exception); - if (exception) { - throw RJSException(ctx, exception); - } - - double utc = JSValueToNumber(ctx, utcVal, &exception); - if (exception) { - throw RJSException(ctx, exception); - } + JSObjectRef object = RJSValidatedValueToDate(ctx, val); + double utc = RJSValidatedValueToNumber(ctx, object); return DateTime(utc); } diff --git a/src/RJSUtil.hpp b/src/RJSUtil.hpp index 0ff3beeb..5e968d0c 100644 --- a/src/RJSUtil.hpp +++ b/src/RJSUtil.hpp @@ -99,6 +99,9 @@ JSValueRef RJSMakeError(JSContextRef ctx, RJSException &exp); JSValueRef RJSMakeError(JSContextRef ctx, std::exception &exp); JSValueRef RJSMakeError(JSContextRef ctx, const std::string &message); +bool RJSIsValueArray(JSContextRef ctx, JSValueRef value); +bool RJSIsValueDate(JSContextRef ctx, JSValueRef value); + static inline JSObjectRef RJSValidatedValueToObject(JSContextRef ctx, JSValueRef value, const char *message = NULL) { JSObjectRef object = JSValueToObject(ctx, value, NULL); if (!object) { @@ -107,6 +110,14 @@ static inline JSObjectRef RJSValidatedValueToObject(JSContextRef ctx, JSValueRef return object; } +static inline JSObjectRef RJSValidatedValueToDate(JSContextRef ctx, JSValueRef value, const char *message = NULL) { + JSObjectRef object = JSValueToObject(ctx, value, NULL); + if (!object || !RJSIsValueDate(ctx, object)) { + throw std::runtime_error(message ?: "Value is not a date."); + } + return object; +} + static inline JSObjectRef RJSValidatedValueToFunction(JSContextRef ctx, JSValueRef value, const char *message = NULL) { JSObjectRef object = JSValueToObject(ctx, value, NULL); if (!object || !JSObjectIsFunction(ctx, object)) { @@ -200,6 +211,3 @@ static inline bool RJSIsValueObjectOfType(JSContextRef ctx, JSValueRef value, JS return ret; } - -bool RJSIsValueArray(JSContextRef ctx, JSValueRef value); - diff --git a/src/RJSUtil.mm b/src/RJSUtil.mm index e64dd061..6429cf3d 100644 --- a/src/RJSUtil.mm +++ b/src/RJSUtil.mm @@ -115,6 +115,10 @@ bool RJSIsValueArray(JSContextRef ctx, JSValueRef value) { return RJSIsValueObjectOfType(ctx, value, arrayString); } +bool RJSIsValueDate(JSContextRef ctx, JSValueRef value) { + static JSStringRef dateString = JSStringCreateWithUTF8CString("Date"); + return RJSIsValueObjectOfType(ctx, value, dateString); +} #include diff --git a/src/RealmRPC.mm b/src/RealmRPC.mm index 1e914fd1..a66b09b8 100644 --- a/src/RealmRPC.mm +++ b/src/RealmRPC.mm @@ -275,6 +275,12 @@ static const char * const RealmObjectTypesResults = "ObjectTypesRESULTS"; } return @{@"value": array}; } + else if (RJSIsValueDate(_context, value)) { + return @{ + @"type": @(RJSTypeGet(realm::PropertyTypeDate).c_str()), + @"value": @(RJSValidatedValueToNumber(_context, value)), + }; + } else { assert(0); } @@ -305,6 +311,8 @@ static const char * const RealmObjectTypesResults = "ObjectTypesRESULTS"; } NSString *type = dict[@"type"]; + id value = dict[@"value"]; + if ([type isEqualToString:@(RealmObjectTypesFunction)]) { // FIXME: Make this actually call the function by its id once we need it to. JSStringRef jsBody = JSStringCreateWithUTF8CString(""); @@ -313,8 +321,17 @@ static const char * const RealmObjectTypesResults = "ObjectTypesRESULTS"; return jsFunction; } + 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; + } - id value = dict[@"value"]; if (!value) { return JSValueMakeUndefined(_context); } From 3439b4c4556325484bc9fefa5bbb57c592f091e4 Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Mon, 19 Oct 2015 16:59:04 -0700 Subject: [PATCH 12/15] Add getter/setter for defaultPath to RPC --- lib/realm.js | 19 +++++++++++-------- lib/rpc.js | 10 ++++++++++ src/RealmRPC.mm | 8 ++++++++ 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/lib/realm.js b/lib/realm.js index b6101efe..bf6f4fe5 100644 --- a/lib/realm.js +++ b/lib/realm.js @@ -103,14 +103,17 @@ util.createMethods(Realm.prototype, objectTypes.REALM, [ 'objects', ]); -Object.defineProperty(Realm, 'Types', { - value: Object.freeze(propTypes), -}); - -Object.defineProperty(Realm, 'clearTestState', { - value: function() { - rpc.clearTestState(); - } +Object.defineProperties(Realm, { + Types: { + value: Object.freeze(propTypes), + }, + defaultPath: { + get: rpc.getDefaultPath, + set: rpc.setDefaultPath, + }, + clearTestState: { + value: rpc.clearTestState, + }, }); module.exports = Realm; diff --git a/lib/rpc.js b/lib/rpc.js index d3813fb5..66885632 100644 --- a/lib/rpc.js +++ b/lib/rpc.js @@ -19,6 +19,8 @@ if (XMLHttpRequest.__proto__ != window.XMLHttpRequestEventTarget) { module.exports = { registerTypeConverter, + getDefaultPath, + setDefaultPath, createRealm, callMethod, getProperty, @@ -34,6 +36,14 @@ function registerTypeConverter(type, handler) { typeConverters[type] = handler; } +function getDefaultPath() { + return sendRequest('get_default_path'); +} + +function setDefaultPath(path) { + sendRequest('set_default_path', {path}); +} + function createRealm(args) { if (args) { args = args.map((arg) => serialize(null, arg)); diff --git a/src/RealmRPC.mm b/src/RealmRPC.mm index a66b09b8..bd089e93 100644 --- a/src/RealmRPC.mm +++ b/src/RealmRPC.mm @@ -70,6 +70,14 @@ static const char * const RealmObjectTypesResults = "ObjectTypesRESULTS"; id _self = self; __weak __typeof__(self) self = _self; + _requests["/get_default_path"] = [=](NSDictionary *dict) { + return @{@"result": @(RJSDefaultPath().c_str())}; + }; + _requests["/set_default_path"] = [=](NSDictionary *dict) { + NSString *path = dict[@"path"]; + RJSSetDefaultPath(path.UTF8String); + return nil; + }; _requests["/create_realm"] = [=](NSDictionary *dict) { NSArray *args = dict[@"arguments"]; NSUInteger argCount = args.count; From 85e2a26b420bae0e7c6626814f1e1fa5c0e2253f Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Mon, 19 Oct 2015 17:28:20 -0700 Subject: [PATCH 13/15] Make RPC Results objects auto-resize --- lib/realm.js | 25 ++++++++++++++++++------- lib/util.js | 43 ++++++++++++++++++++++++------------------- 2 files changed, 42 insertions(+), 26 deletions(-) diff --git a/lib/realm.js b/lib/realm.js index bf6f4fe5..4b0cee12 100644 --- a/lib/realm.js +++ b/lib/realm.js @@ -11,6 +11,7 @@ let util = require('./util'); let {keys, propTypes, objectTypes} = constants; let notificationsKey = Symbol(); let notificationCallbackKey = Symbol(); +let resultsKey = Symbol(); // TODO: DATA rpc.registerTypeConverter(propTypes.DATE, (_, info) => new Date(info.value)); @@ -42,6 +43,7 @@ class Realm { this[keys.realm] = realmId; this[keys.type] = objectTypes.REALM; this[notificationsKey] = []; + this[resultsKey] = []; [ 'path', @@ -52,19 +54,25 @@ class Realm { } addNotification(callback) { - let realmId = this[keys.realm]; - - if (!realmId) { - throw new TypeError('addNotification method was not called on a Realm object!'); - } if (typeof callback != 'function') { throw new Error('Realm.addNotification must be passed a function!'); } - let notification = rpc.callMethod(realmId, realmId, 'addNotification', [callback]); + let method = util.createMethod(objectTypes.REALM, 'addNotification'); + let notification = method.apply(this, arguments); + notification[notificationCallbackKey] = callback; this[notificationsKey].push(notification); + return notification; + } + + objects() { + let method = util.createMethod(objectTypes.REALM, 'objects'); + let results = method.apply(this, arguments); + + this[resultsKey].push(results); + return results; } write(callback) { @@ -88,6 +96,10 @@ class Realm { rpc.commitTransaction(realmId); + for (let results of this[resultsKey]) { + results[keys.resize](); + } + for (let notification of this[notificationsKey]) { let callback = notification[notificationCallbackKey]; callback(this, 'DidChangeNotification'); @@ -100,7 +112,6 @@ util.createMethods(Realm.prototype, objectTypes.REALM, [ 'create', 'delete', 'deleteAll', - 'objects', ]); Object.defineProperties(Realm, { diff --git a/lib/util.js b/lib/util.js index ba14ede7..699662dc 100644 --- a/lib/util.js +++ b/lib/util.js @@ -8,6 +8,7 @@ let {keys} = constants; module.exports = { createList, createMethods, + createMethod, getterForProperty, setterForProperty, }; @@ -62,31 +63,35 @@ function createMethods(prototype, type, methodNames, resize) { for (let name of methodNames) { props[name] = { - value: function() { - let realmId = this[keys.realm]; - let id = this[keys.id]; - - if (!realmId || !id) { - throw new TypeError(name + ' method was not called a Realm object!'); - } - if (this[keys.type] !== type) { - throw new TypeError(name + ' method was called on an object of the wrong type!'); - } - - let result = rpc.callMethod(realmId, id, name, Array.from(arguments)); - - if (resize) { - this[keys.resize](); - } - - return result; - } + value: createMethod(type, name, resize), }; } Object.defineProperties(prototype, props); } +function createMethod(type, name, resize) { + return function() { + let realmId = this[keys.realm]; + let id = this[keys.id]; + + if (!realmId || !id) { + throw new TypeError(name + ' method was not called a Realm object!'); + } + if (this[keys.type] !== type) { + throw new TypeError(name + ' method was called on an object of the wrong type!'); + } + + let result = rpc.callMethod(realmId, id, name, Array.from(arguments)); + + if (resize) { + this[keys.resize](); + } + + return result; + }; +} + function getterForProperty(name) { return function() { return rpc.getProperty(this[keys.realm], this[keys.id], name); From f2c4e7882a177ce235b65acc02cf8584470c7988 Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Mon, 19 Oct 2015 17:43:51 -0700 Subject: [PATCH 14/15] Make RPC lists non-extensible Assigning to non-existent index will throw an exception and all tests pass in Chrome! --- lib/lists.js | 7 ++++++- lib/util.js | 6 +++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/lists.js b/lib/lists.js index 5ee8a6f7..b11d4b1f 100644 --- a/lib/lists.js +++ b/lib/lists.js @@ -18,5 +18,10 @@ util.createMethods(List.prototype, constants.propTypes.LIST, [ ], true); function create(realmId, info) { - return util.createList(List.prototype, realmId, info, true); + let meta = util.createList(List.prototype, realmId, info, true); + let list = Object.create(meta); + + Object.preventExtensions(list); + + return list; } diff --git a/lib/util.js b/lib/util.js index 699662dc..9bd68db9 100644 --- a/lib/util.js +++ b/lib/util.js @@ -21,7 +21,7 @@ function createList(prototype, realmId, info, mutable) { list[keys.resize] = function(length) { if (length == null) { - length = this.length; + length = list.length; } if (length == size) { return; @@ -39,11 +39,11 @@ function createList(prototype, realmId, info, mutable) { }; } - Object.defineProperties(this, props); + Object.defineProperties(list, props); } else if (length < size) { for (let i = size - 1; i >= length; i--) { - delete this[i]; + delete list[i]; } } From 01dc3a85d9f9d5999c5297202cd9eddd0c1a347f Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Tue, 20 Oct 2015 00:52:38 -0700 Subject: [PATCH 15/15] Add comment about usage of preventExtensions --- lib/lists.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/lists.js b/lib/lists.js index b11d4b1f..164df2c8 100644 --- a/lib/lists.js +++ b/lib/lists.js @@ -21,6 +21,7 @@ function create(realmId, info) { let meta = util.createList(List.prototype, realmId, info, true); let list = Object.create(meta); + // This will make attempts at assigning to out-of-bounds indices throw an exception. Object.preventExtensions(list); return list;