Merge pull request #63 from realm/sk-chrome-apis

Finish implementing APIs for Chrome
This commit is contained in:
Scott Kyle 2015-10-20 01:23:40 -07:00
commit b98e89db22
20 changed files with 405 additions and 369 deletions

48
lib/constants.js Normal file
View File

@ -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
};

View File

@ -1,63 +1,28 @@
'use strict'; 'use strict';
let rpc = require('./rpc'); let constants = require('./constants');
let util = require('./util'); let util = require('./util');
let idKey = util.idKey; module.exports = {
let realmKey = util.realmKey; create,
let resizeListKey = util.resizeListKey; };
let prototype = {};
exports.create = create; class List {}
[ util.createMethods(List.prototype, constants.propTypes.LIST, [
'pop', 'pop',
'shift', 'shift',
'push', 'push',
'unshift', 'unshift',
'splice', 'splice',
].forEach(function(name) { ], true);
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;
}
});
});
function create(realmId, info) { function create(realmId, info) {
let list = util.createList(prototype, getterForLength, getterForIndex, setterForIndex); let meta = util.createList(List.prototype, realmId, info, true);
let size = info.size; let list = Object.create(meta);
list[realmKey] = realmId; // This will make attempts at assigning to out-of-bounds indices throw an exception.
list[idKey] = info.id; Object.preventExtensions(list);
list[resizeListKey](size);
return list; return list;
} }
function getterForLength() {
return rpc.getListSize(this[realmKey], this[idKey]);
}
function getterForIndex(index) {
return function() {
return rpc.getListItem(this[realmKey], this[idKey], index);
};
}
function setterForIndex(index) {
return function(value) {
rpc.setListItem(this[realmKey], this[idKey], index, value);
};
}

21
lib/notifications.js Normal file
View File

@ -0,0 +1,21 @@
'use strict';
let constants = require('./constants');
let {keys} = constants;
module.exports = {
create,
};
class Notification {}
function create(realmId, info) {
let notification = new Notification();
notification[keys.realm] = realmId;
notification[keys.id] = info.id;
notification[keys.type] = info.type;
return notification;
}

View File

@ -1,14 +1,15 @@
'use strict'; 'use strict';
let rpc = require('./rpc'); let constants = require('./constants');
let util = require('./util'); let util = require('./util');
let idKey = util.idKey; let {keys} = constants;
let realmKey = util.realmKey;
let registeredConstructors = {}; let registeredConstructors = {};
exports.create = create; module.exports = {
exports.registerConstructors = registerConstructors; create,
registerConstructors,
};
function create(realmId, info) { function create(realmId, info) {
let schema = info.schema; let schema = info.schema;
@ -16,15 +17,16 @@ function create(realmId, info) {
let object = constructor ? Object.create(constructor.prototype) : {}; let object = constructor ? Object.create(constructor.prototype) : {};
let props = {}; let props = {};
object[realmKey] = realmId; object[keys.realm] = realmId;
object[idKey] = info.id; object[keys.id] = info.id;
object[keys.type] = info.type;
for (let prop of schema.properties) { for (let prop of schema.properties) {
let name = prop.name; let name = prop.name;
props[name] = { props[name] = {
get: getterForProperty(name), get: util.getterForProperty(name),
set: setterForProperty(name), set: util.setterForProperty(name),
}; };
} }
@ -36,15 +38,3 @@ function create(realmId, info) {
function registerConstructors(realmId, constructors) { function registerConstructors(realmId, constructors) {
registeredConstructors[realmId] = constructors; registeredConstructors[realmId] = constructors;
} }
function getterForProperty(name) {
return function() {
return rpc.getObjectProperty(this[realmKey], this[idKey], name);
};
}
function setterForProperty(name) {
return function(value) {
rpc.setObjectProperty(this[realmKey], this[idKey], name, value);
};
}

View File

@ -1,19 +1,24 @@
'use strict'; 'use strict';
let constants = require('./constants');
let lists = require('./lists'); let lists = require('./lists');
let objects = require('./objects'); let objects = require('./objects');
let notifications = require('./notifications');
let results = require('./results'); let results = require('./results');
let rpc = require('./rpc'); let rpc = require('./rpc');
let types = require('./types');
let util = require('./util'); let util = require('./util');
let realmKey = util.realmKey; let {keys, propTypes, objectTypes} = constants;
let notificationsKey = Symbol();
let notificationCallbackKey = Symbol();
let resultsKey = Symbol();
// TODO: DATA // TODO: DATA
rpc.registerTypeConverter(types.DATE, (_, info) => new Date(info.value)); rpc.registerTypeConverter(propTypes.DATE, (_, info) => new Date(info.value));
rpc.registerTypeConverter(types.LIST, lists.create); rpc.registerTypeConverter(propTypes.LIST, lists.create);
rpc.registerTypeConverter(types.OBJECT, objects.create); rpc.registerTypeConverter(propTypes.OBJECT, objects.create);
rpc.registerTypeConverter('ObjectTypesRESULTS', results.create); rpc.registerTypeConverter(objectTypes.NOTIFICATION, notifications.create);
rpc.registerTypeConverter(objectTypes.RESULTS, results.create);
class Realm { class Realm {
constructor(config) { constructor(config) {
@ -30,17 +35,48 @@ class Realm {
} }
} }
let realmId = this[realmKey] = rpc.createRealm(Array.from(arguments)); let realmId = rpc.createRealm(Array.from(arguments));
objects.registerConstructors(realmId, constructors); objects.registerConstructors(realmId, constructors);
this[keys.id] = realmId;
this[keys.realm] = realmId;
this[keys.type] = objectTypes.REALM;
this[notificationsKey] = [];
this[resultsKey] = [];
[
'path',
'schemaVersion',
].forEach((name) => {
Object.defineProperty(this, name, {get: util.getterForProperty(name)});
});
} }
addNotification(callback) { addNotification(callback) {
// TODO if (typeof callback != 'function') {
throw new Error('Realm.addNotification must be passed a function!');
}
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) { write(callback) {
let realmId = this[realmKey]; let realmId = this[keys.realm];
if (!realmId) { if (!realmId) {
throw new TypeError('write method was not called on a Realm object!'); throw new TypeError('write method was not called on a Realm object!');
@ -59,34 +95,36 @@ class Realm {
} }
rpc.commitTransaction(realmId); 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');
}
} }
} }
[ util.createMethods(Realm.prototype, objectTypes.REALM, [
'close',
'create', 'create',
'delete', 'delete',
'deleteAll', 'deleteAll',
'objects', ]);
].forEach(function(name) {
Object.defineProperty(Realm.prototype, name, {
value: function() {
let realmId = this[realmKey];
if (!realmId) { Object.defineProperties(Realm, {
throw new TypeError(name + ' method was not called on a Realm object!'); Types: {
} value: Object.freeze(propTypes),
},
return rpc.callRealmMethod(realmId, name, Array.from(arguments)); defaultPath: {
} get: rpc.getDefaultPath,
}); set: rpc.setDefaultPath,
}); },
clearTestState: {
Object.defineProperty(Realm, 'Types', {value: types}); value: rpc.clearTestState,
},
Object.defineProperty(Realm, 'clearTestState', {
value: function() {
rpc.clearTestState();
}
}); });
module.exports = Realm; module.exports = Realm;

View File

@ -1,32 +1,18 @@
'use strict'; 'use strict';
let rpc = require('./rpc'); let constants = require('./constants');
let util = require('./util'); let util = require('./util');
let idKey = util.idKey; module.exports = {
let realmKey = util.realmKey; create,
let resizeListKey = util.resizeListKey; };
exports.create = create; class Results {}
util.createMethods(Results.prototype, constants.objectTypes.RESULTS, [
'sortByProperty',
]);
function create(realmId, info) { function create(realmId, info) {
let results = util.createList(null, getterForLength, getterForIndex); return util.createList(Results.prototype, realmId, info);
let size = info.size;
results[realmKey] = realmId;
results[idKey] = info.id;
results[resizeListKey](size);
return results;
}
function getterForLength() {
return rpc.getResultsSize(this[realmKey], this[idKey]);
}
function getterForIndex(index) {
return function() {
return rpc.getResultsItem(this[realmKey], this[idKey], index);
};
} }

View File

@ -1,11 +1,10 @@
'use strict'; 'use strict';
let util = require('./util'); let constants = require('./constants');
let DEVICE_HOST = 'localhost:8082'; let DEVICE_HOST = 'localhost:8082';
let idKey = util.idKey; let {keys, objectTypes, propTypes} = constants;
let realmKey = util.realmKey;
let typeConverters = {}; let typeConverters = {};
let XMLHttpRequest = window.XMLHttpRequest; let XMLHttpRequest = window.XMLHttpRequest;
@ -17,32 +16,34 @@ if (XMLHttpRequest.__proto__ != window.XMLHttpRequestEventTarget) {
window.XMLHttpRequest = override; window.XMLHttpRequest = override;
} }
exports.registerTypeConverter = registerTypeConverter; module.exports = {
registerTypeConverter,
exports.createRealm = createRealm; getDefaultPath,
exports.callRealmMethod = callRealmMethod; setDefaultPath,
createRealm,
callMethod,
getProperty,
setProperty,
beginTransaction,
cancelTransaction,
commitTransaction,
exports.getObjectProperty = getObjectProperty; clearTestState,
exports.setObjectProperty = setObjectProperty; };
exports.getListItem = getListItem;
exports.setListItem = setListItem;
exports.getListSize = getListSize;
exports.callListMethod = callListMethod;
exports.getResultsItem = getResultsItem;
exports.getResultsSize = getResultsSize;
exports.beginTransaction = beginTransaction;
exports.cancelTransaction = cancelTransaction;
exports.commitTransaction = commitTransaction;
exports.clearTestState = clearTestState;
function registerTypeConverter(type, handler) { function registerTypeConverter(type, handler) {
typeConverters[type] = handler; typeConverters[type] = handler;
} }
function getDefaultPath() {
return sendRequest('get_default_path');
}
function setDefaultPath(path) {
sendRequest('set_default_path', {path});
}
function createRealm(args) { function createRealm(args) {
if (args) { if (args) {
args = args.map((arg) => serialize(null, arg)); args = args.map((arg) => serialize(null, arg));
@ -51,54 +52,23 @@ function createRealm(args) {
return sendRequest('create_realm', {arguments: args}); return sendRequest('create_realm', {arguments: args});
} }
function callRealmMethod(realmId, name, args) { function callMethod(realmId, id, name, args) {
if (args) { if (args) {
args = args.map((arg) => serialize(realmId, arg)); args = args.map((arg) => serialize(realmId, arg));
} }
let result = sendRequest('call_realm_method', {realmId, name, arguments: args}); let result = sendRequest('call_method', {realmId, id, name, arguments: args});
return deserialize(realmId, result); return deserialize(realmId, result);
} }
function getObjectProperty(realmId, objectId, name) { function getProperty(realmId, id, name) {
let result = sendRequest('get_property', {realmId, objectId, name}); let result = sendRequest('get_property', {realmId, id, name});
return deserialize(realmId, result); return deserialize(realmId, result);
} }
function setObjectProperty(realmId, objectId, name, value) { function setProperty(realmId, id, name, value) {
value = serialize(realmId, 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});
return deserialize(realmId, result);
}
function setListItem(realmId, listId, index, value) {
sendRequest('set_list_item', {realmId, listId, index, value: serialize(realmId, value)});
}
function getListSize(realmId, listId) {
return sendRequest('get_list_size', {realmId, listId});
}
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});
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 beginTransaction(realmId) { function beginTransaction(realmId) {
@ -118,17 +88,25 @@ function clearTestState() {
} }
function serialize(realmId, value) { function serialize(realmId, value) {
if (typeof value == 'function') {
return {type: objectTypes.FUNCTION};
}
if (!value || typeof value != 'object') { if (!value || typeof value != 'object') {
return {value: value}; return {value: value};
} }
let id = value[idKey]; let id = value[keys.id];
if (id) { if (id) {
if (value[realmKey] != realmId) { if (value[keys.realm] != realmId) {
throw new Error('Unable to serialize value from another Realm'); throw new Error('Unable to serialize value from another Realm');
} }
return {id: id}; return {id};
}
if (value instanceof Date) {
return {type: propTypes.DATE, value: value.getTime()};
} }
if (Array.isArray(value)) { if (Array.isArray(value)) {

View File

@ -1,19 +0,0 @@
'use strict';
let types = {};
[
'BOOL',
'INT',
'FLOAT',
'DOUBLE',
'STRING',
'DATE',
'DATA',
'OBJECT',
'LIST',
].forEach(function(type) {
types[type] = 'PropTypes' + type;
});
module.exports = Object.freeze(types);

View File

@ -1,20 +1,27 @@
'use strict'; 'use strict';
let idKey = exports.idKey = Symbol(); let constants = require('./constants');
let realmKey = exports.realmKey = Symbol(); let rpc = require('./rpc');
let resizeListKey = exports.resizeListKey = Symbol();
exports.createList = createList; let {keys} = constants;
function createList(prototype, getterForLength, getterForIndex, setterForIndex) { module.exports = {
var list = prototype ? Object.create(prototype) : {}; createList,
var size = 0; createMethods,
createMethod,
getterForProperty,
setterForProperty,
};
Object.defineProperty(list, 'length', {get: getterForLength}); function createList(prototype, realmId, info, mutable) {
let list = Object.create(prototype);
let size = 0;
list[resizeListKey] = function(length) { Object.defineProperty(list, 'length', {get: getterForProperty('length')});
list[keys.resize] = function(length) {
if (length == null) { if (length == null) {
length = this.length; length = list.length;
} }
if (length == size) { if (length == size) {
return; return;
@ -25,23 +32,74 @@ function createList(prototype, getterForLength, getterForIndex, setterForIndex)
for (let i = size; i < length; i++) { for (let i = size; i < length; i++) {
props[i] = { props[i] = {
get: getterForIndex(i), get: getterForProperty(i),
set: setterForIndex && setterForIndex(i), set: mutable ? setterForProperty(i) : undefined,
enumerable: true, enumerable: true,
configurable: true, configurable: true,
}; };
} }
Object.defineProperties(this, props); Object.defineProperties(list, props);
} }
else if (length < size) { else if (length < size) {
for (let i = size - 1; i >= length; i--) { for (let i = size - 1; i >= length; i--) {
delete this[i]; delete list[i];
} }
} }
size = length; size = length;
}; };
list[keys.realm] = realmId;
list[keys.id] = info.id;
list[keys.type] = info.type;
list[keys.resize](info.size);
return list; return list;
} }
function createMethods(prototype, type, methodNames, resize) {
let props = {};
for (let name of methodNames) {
props[name] = {
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);
};
}
function setterForProperty(name) {
return function(value) {
rpc.setProperty(this[keys.realm], this[keys.id], name, value);
};
}

View File

@ -214,7 +214,7 @@ JSObjectRef RJSListCreate(JSContextRef ctx, realm::List &list) {
return RJSWrapObject<List *>(ctx, RJSListClass(), new List(list)); return RJSWrapObject<List *>(ctx, RJSListClass(), new List(list));
} }
const JSStaticFunction RJSListFuncs[] = { static const JSStaticFunction RJSListFuncs[] = {
{"push", ListPush, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, {"push", ListPush, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete},
{"pop", ListPop, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, {"pop", ListPop, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete},
{"shift", ListShift, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, {"shift", ListShift, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete},

View File

@ -20,9 +20,5 @@
#import "shared_realm.hpp" #import "shared_realm.hpp"
#import "list.hpp" #import "list.hpp"
extern const JSStaticFunction RJSListFuncs[];
JSClassRef RJSListClass(); JSClassRef RJSListClass();
JSObjectRef RJSListCreate(JSContextRef ctx, realm::List &list); 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);

View File

@ -24,7 +24,3 @@ namespace realm {
JSClassRef RJSObjectClass(); JSClassRef RJSObjectClass();
JSObjectRef RJSObjectCreate(JSContextRef ctx, realm::Object object); 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);

View File

@ -143,21 +143,8 @@ template<> JSValueRef RJSAccessor::from_string(JSContextRef ctx, StringData s) {
} }
template<> DateTime RJSAccessor::to_datetime(JSContextRef ctx, JSValueRef &val) { template<> DateTime RJSAccessor::to_datetime(JSContextRef ctx, JSValueRef &val) {
JSObjectRef object = RJSValidatedValueToObject(ctx, val, "Property must be a Date"); JSObjectRef object = RJSValidatedValueToDate(ctx, val);
double utc = RJSValidatedValueToNumber(ctx, object);
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);
}
return DateTime(utc); return DateTime(utc);
} }

View File

@ -24,8 +24,6 @@ namespace realm {
using ObjectDefaults = std::map<std::string, JSValueRef>; using ObjectDefaults = std::map<std::string, JSValueRef>;
} }
extern const JSStaticFunction RJSRealmFuncs[];
JSClassRef RJSRealmClass(); JSClassRef RJSRealmClass();
JSClassRef RJSRealmConstructorClass(); JSClassRef RJSRealmConstructorClass();
JSClassRef RJSNotificationClass(); JSClassRef RJSNotificationClass();

View File

@ -466,7 +466,7 @@ JSValueRef RealmClose(JSContextRef ctx, JSObjectRef function, JSObjectRef thisOb
return NULL; return NULL;
} }
const JSStaticFunction RJSRealmFuncs[] = { static const JSStaticFunction RJSRealmFuncs[] = {
{"objects", RealmObjects, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, {"objects", RealmObjects, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete},
{"create", RealmCreateObject, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, {"create", RealmCreateObject, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete},
{"delete", RealmDelete, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, {"delete", RealmDelete, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete},

View File

@ -26,6 +26,3 @@ namespace realm {
JSClassRef RJSResultsClass(); JSClassRef RJSResultsClass();
JSObjectRef RJSResultsCreate(JSContextRef ctx, realm::SharedRealm realm, std::string className); JSObjectRef RJSResultsCreate(JSContextRef ctx, realm::SharedRealm realm, std::string className);
JSObjectRef RJSResultsCreate(JSContextRef ctx, realm::SharedRealm realm, std::string className, std::string query); JSObjectRef RJSResultsCreate(JSContextRef ctx, realm::SharedRealm realm, std::string className, std::string query);
JSValueRef ResultsGetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* jsException);

View File

@ -118,12 +118,12 @@ JSObjectRef RJSResultsCreate(JSContextRef ctx, SharedRealm realm, std::string cl
return RJSWrapObject<Results *>(ctx, RJSResultsClass(), new Results(realm, *object_schema, std::move(query))); return RJSWrapObject<Results *>(ctx, RJSResultsClass(), new Results(realm, *object_schema, std::move(query)));
} }
static const JSStaticFunction RJSResultsFuncs[] = {
JSClassRef RJSResultsClass() {
const JSStaticFunction resultsFuncs[] = {
{"sortByProperty", SortByProperty, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, {"sortByProperty", SortByProperty, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete},
{NULL, NULL}, {NULL, NULL},
}; };
static JSClassRef s_objectClass = RJSCreateWrapperClass<Results *>("Results", ResultsGetProperty, NULL, resultsFuncs, ResultsPropertyNames);
JSClassRef RJSResultsClass() {
static JSClassRef s_objectClass = RJSCreateWrapperClass<Results *>("Results", ResultsGetProperty, NULL, RJSResultsFuncs, ResultsPropertyNames);
return s_objectClass; return s_objectClass;
} }

View File

@ -99,6 +99,9 @@ JSValueRef RJSMakeError(JSContextRef ctx, RJSException &exp);
JSValueRef RJSMakeError(JSContextRef ctx, std::exception &exp); JSValueRef RJSMakeError(JSContextRef ctx, std::exception &exp);
JSValueRef RJSMakeError(JSContextRef ctx, const std::string &message); 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) { static inline JSObjectRef RJSValidatedValueToObject(JSContextRef ctx, JSValueRef value, const char *message = NULL) {
JSObjectRef object = JSValueToObject(ctx, value, NULL); JSObjectRef object = JSValueToObject(ctx, value, NULL);
if (!object) { if (!object) {
@ -107,6 +110,14 @@ static inline JSObjectRef RJSValidatedValueToObject(JSContextRef ctx, JSValueRef
return object; 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) { static inline JSObjectRef RJSValidatedValueToFunction(JSContextRef ctx, JSValueRef value, const char *message = NULL) {
JSObjectRef object = JSValueToObject(ctx, value, NULL); JSObjectRef object = JSValueToObject(ctx, value, NULL);
if (!object || !JSObjectIsFunction(ctx, object)) { if (!object || !JSObjectIsFunction(ctx, object)) {
@ -200,6 +211,3 @@ static inline bool RJSIsValueObjectOfType(JSContextRef ctx, JSValueRef value, JS
return ret; return ret;
} }
bool RJSIsValueArray(JSContextRef ctx, JSValueRef value);

View File

@ -115,6 +115,10 @@ bool RJSIsValueArray(JSContextRef ctx, JSValueRef value) {
return RJSIsValueObjectOfType(ctx, value, arrayString); return RJSIsValueObjectOfType(ctx, value, arrayString);
} }
bool RJSIsValueDate(JSContextRef ctx, JSValueRef value) {
static JSStringRef dateString = JSStringCreateWithUTF8CString("Date");
return RJSIsValueObjectOfType(ctx, value, dateString);
}
#include <realm.hpp> #include <realm.hpp>

View File

@ -19,6 +19,7 @@
#import "RealmRPC.h" #import "RealmRPC.h"
#import <JavaScriptCore/JavaScriptCore.h> #import <JavaScriptCore/JavaScriptCore.h>
#include <dlfcn.h>
#include <map> #include <map>
#include <string> #include <string>
#include "RealmJS.h" #include "RealmJS.h"
@ -35,6 +36,10 @@
using RPCObjectID = u_int64_t; using RPCObjectID = u_int64_t;
using RPCRequest = std::function<NSDictionary *(NSDictionary *dictionary)>; using RPCRequest = std::function<NSDictionary *(NSDictionary *dictionary)>;
static const char * const RealmObjectTypesFunction = "ObjectTypesFUNCTION";
static const char * const RealmObjectTypesNotification = "ObjectTypesNOTIFICATION";
static const char * const RealmObjectTypesResults = "ObjectTypesRESULTS";
@implementation RJSRPCServer { @implementation RJSRPCServer {
JSGlobalContextRef _context; JSGlobalContextRef _context;
std::map<std::string, RPCRequest> _requests; std::map<std::string, RPCRequest> _requests;
@ -55,9 +60,24 @@ using RPCRequest = std::function<NSDictionary *(NSDictionary *dictionary)>;
if (self) { if (self) {
_context = JSGlobalContextCreate(NULL); _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; id _self = self;
__weak __typeof__(self) 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) { _requests["/create_realm"] = [=](NSDictionary *dict) {
NSArray *args = dict[@"arguments"]; NSArray *args = dict[@"arguments"];
NSUInteger argCount = args.count; NSUInteger argCount = args.count;
@ -92,36 +112,50 @@ using RPCRequest = std::function<NSDictionary *(NSDictionary *dictionary)>;
RJSGetInternal<realm::SharedRealm *>(_objects[realmId])->get()->commit_transaction(); RJSGetInternal<realm::SharedRealm *>(_objects[realmId])->get()->commit_transaction();
return nil; return nil;
}; };
_requests["/call_realm_method"] = [=](NSDictionary *dict) { _requests["/call_method"] = [=](NSDictionary *dict) {
NSString *name = dict[@"name"]; return [self performObjectMethod:dict[@"name"]
return [self performObjectMethod:name.UTF8String
classMethods:RJSRealmFuncs
args:dict[@"arguments"] args:dict[@"arguments"]
objectId:[dict[@"realmId"] unsignedLongValue]]; objectId:[dict[@"id"] unsignedLongValue]];
}; };
_requests["/get_property"] = [=](NSDictionary *dict) { _requests["/get_property"] = [=](NSDictionary *dict) {
RPCObjectID oid = [dict[@"id"] unsignedLongValue];
id name = dict[@"name"];
JSValueRef value = NULL;
JSValueRef exception = NULL; JSValueRef exception = NULL;
NSString *name = dict[@"name"];
JSStringRef propString = RJSStringForString(name.UTF8String); if ([name isKindOfClass:[NSNumber class]]) {
RPCObjectID objectId = [dict[@"objectId"] unsignedLongValue]; value = JSObjectGetPropertyAtIndex(_context, _objects[oid], [name unsignedIntValue], &exception);
JSValueRef propertyValue = ObjectGetProperty(_context, _objects[objectId], propString, &exception); }
else {
JSStringRef propString = RJSStringForString([name UTF8String]);
value = JSObjectGetProperty(_context, _objects[oid], propString, &exception);
JSStringRelease(propString); JSStringRelease(propString);
}
if (exception) { if (exception) {
return @{@"error": @(RJSStringForValue(_context, exception).c_str())}; return @{@"error": @(RJSStringForValue(_context, exception).c_str())};
} }
return @{@"result": [self resultForJSValue:propertyValue]}; return @{@"result": [self resultForJSValue:value]};
}; };
_requests["/set_property"] = [=](NSDictionary *dict) { _requests["/set_property"] = [=](NSDictionary *dict) {
JSStringRef propString = RJSStringForString([dict[@"name"] UTF8String]); RPCObjectID oid = [dict[@"id"] unsignedLongValue];
RPCObjectID realmId = [dict[@"objectId"] unsignedLongValue]; id name = dict[@"name"];
JSValueRef value = [self deserializeDictionaryValue:dict[@"value"]]; JSValueRef value = [self deserializeDictionaryValue:dict[@"value"]];
JSValueRef exception = NULL; JSValueRef exception = NULL;
ObjectSetProperty(_context, _objects[realmId], propString, value, &exception); 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); 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) { _requests["/dispose_object"] = [=](NSDictionary *dict) {
RPCObjectID oid = [dict[@"id"] unsignedLongValue]; RPCObjectID oid = [dict[@"id"] unsignedLongValue];
@ -129,84 +163,6 @@ using RPCRequest = std::function<NSDictionary *(NSDictionary *dictionary)>;
_objects.erase(oid); _objects.erase(oid);
return nil; return nil;
}; };
_requests["/get_results_size"] = [=](NSDictionary *dict) {
RPCObjectID resultsId = [dict[@"resultsId"] 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[@"resultsId"] 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[@"listId"] 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[@"listId"] 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[@"listId"] 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["/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) { _requests["/clear_test_state"] = [=](NSDictionary *dict) {
for (auto object : _objects) { for (auto object : _objects) {
JSValueUnprotect(_context, object.second); JSValueUnprotect(_context, object.second);
@ -236,30 +192,25 @@ using RPCRequest = std::function<NSDictionary *(NSDictionary *dictionary)>;
return response ?: @{}; return response ?: @{};
} }
- (NSDictionary *)performObjectMethod:(const char *)name - (NSDictionary *)performObjectMethod:(NSString *)method args:(NSArray *)args objectId:(RPCObjectID)oid {
classMethods:(const JSStaticFunction [])methods JSObjectRef object = _objects[oid];
args:(NSArray *)args JSStringRef methodString = RJSStringForString(method.UTF8String);
objectId:(RPCObjectID)oid { JSObjectRef function = RJSValidatedObjectProperty(_context, object, methodString);
JSStringRelease(methodString);
NSUInteger argCount = args.count; NSUInteger argCount = args.count;
JSValueRef argValues[argCount]; JSValueRef argValues[argCount];
for (NSUInteger i = 0; i < argCount; i++) { for (NSUInteger i = 0; i < argCount; i++) {
argValues[i] = [self deserializeDictionaryValue:args[i]]; argValues[i] = [self deserializeDictionaryValue:args[i]];
} }
size_t index = 0;
while (methods[index].name) {
if (!strcmp(methods[index].name, name)) {
JSValueRef exception = NULL; JSValueRef exception = NULL;
JSValueRef ret = methods[index].callAsFunction(_context, NULL, _objects[oid], argCount, argValues, &exception); JSValueRef result = JSObjectCallAsFunction(_context, function, object, argCount, argValues, &exception);
if (exception) { if (exception) {
return @{@"error": @(RJSStringForValue(_context, exception).c_str())}; return @{@"error": @(RJSStringForValue(_context, exception).c_str())};
} }
return @{@"result": [self resultForJSValue:ret]}; return @{@"result": [self resultForJSValue:result]};
}
index++;
}
return @{@"error": @"invalid method"};
} }
- (RPCObjectID)storeObject:(JSObjectRef)object { - (RPCObjectID)storeObject:(JSObjectRef)object {
@ -311,12 +262,19 @@ using RPCRequest = std::function<NSDictionary *(NSDictionary *dictionary)>;
realm::Results *results = RJSGetInternal<realm::Results *>(jsObject); realm::Results *results = RJSGetInternal<realm::Results *>(jsObject);
RPCObjectID oid = [self storeObject:jsObject]; RPCObjectID oid = [self storeObject:jsObject];
return @{ return @{
@"type": @"ObjectTypesRESULTS", @"type": @(RealmObjectTypesResults),
@"id": @(oid), @"id": @(oid),
@"size": @(results->size()), @"size": @(results->size()),
@"schema": [self objectSchemaToJSONObject:results->object_schema] @"schema": [self objectSchemaToJSONObject:results->object_schema]
}; };
} }
else if (JSValueIsObjectOfClass(_context, value, RJSNotificationClass())) {
RPCObjectID oid = [self storeObject:jsObject];
return @{
@"type": @(RealmObjectTypesNotification),
@"id": @(oid),
};
}
else if (RJSIsValueArray(_context, value)) { else if (RJSIsValueArray(_context, value)) {
size_t length = RJSValidatedListLength(_context, jsObject); size_t length = RJSValidatedListLength(_context, jsObject);
NSMutableArray *array = [NSMutableArray new]; NSMutableArray *array = [NSMutableArray new];
@ -325,6 +283,12 @@ using RPCRequest = std::function<NSDictionary *(NSDictionary *dictionary)>;
} }
return @{@"value": array}; return @{@"value": array};
} }
else if (RJSIsValueDate(_context, value)) {
return @{
@"type": @(RJSTypeGet(realm::PropertyTypeDate).c_str()),
@"value": @(RJSValidatedValueToNumber(_context, value)),
};
}
else { else {
assert(0); assert(0);
} }
@ -354,7 +318,28 @@ using RPCRequest = std::function<NSDictionary *(NSDictionary *dictionary)>;
return _objects[oid]; return _objects[oid];
} }
NSString *type = dict[@"type"];
id value = dict[@"value"]; 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("");
JSObjectRef jsFunction = JSObjectMakeFunction(_context, NULL, 0, NULL, jsBody, NULL, 1, NULL);
JSStringRelease(jsBody);
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;
}
if (!value) { if (!value) {
return JSValueMakeUndefined(_context); return JSValueMakeUndefined(_context);
} }