Merge pull request #63 from realm/sk-chrome-apis
Finish implementing APIs for Chrome
This commit is contained in:
commit
b98e89db22
|
@ -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
|
||||
};
|
57
lib/lists.js
57
lib/lists.js
|
@ -1,63 +1,28 @@
|
|||
'use strict';
|
||||
|
||||
let rpc = require('./rpc');
|
||||
let constants = require('./constants');
|
||||
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, constants.propTypes.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;
|
||||
let meta = util.createList(List.prototype, realmId, info, true);
|
||||
let list = Object.create(meta);
|
||||
|
||||
list[realmKey] = realmId;
|
||||
list[idKey] = info.id;
|
||||
|
||||
list[resizeListKey](size);
|
||||
// This will make attempts at assigning to out-of-bounds indices throw an exception.
|
||||
Object.preventExtensions(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);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -1,14 +1,15 @@
|
|||
'use strict';
|
||||
|
||||
let rpc = require('./rpc');
|
||||
let constants = require('./constants');
|
||||
let util = require('./util');
|
||||
|
||||
let idKey = util.idKey;
|
||||
let realmKey = util.realmKey;
|
||||
let {keys} = constants;
|
||||
let registeredConstructors = {};
|
||||
|
||||
exports.create = create;
|
||||
exports.registerConstructors = registerConstructors;
|
||||
module.exports = {
|
||||
create,
|
||||
registerConstructors,
|
||||
};
|
||||
|
||||
function create(realmId, info) {
|
||||
let schema = info.schema;
|
||||
|
@ -16,15 +17,16 @@ 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;
|
||||
|
||||
props[name] = {
|
||||
get: getterForProperty(name),
|
||||
set: setterForProperty(name),
|
||||
get: util.getterForProperty(name),
|
||||
set: util.setterForProperty(name),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -36,15 +38,3 @@ function create(realmId, info) {
|
|||
function registerConstructors(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);
|
||||
};
|
||||
}
|
||||
|
|
98
lib/realm.js
98
lib/realm.js
|
@ -1,19 +1,24 @@
|
|||
'use strict';
|
||||
|
||||
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 realmKey = util.realmKey;
|
||||
let {keys, propTypes, objectTypes} = constants;
|
||||
let notificationsKey = Symbol();
|
||||
let notificationCallbackKey = Symbol();
|
||||
let resultsKey = 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('ObjectTypesRESULTS', 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) {
|
||||
|
@ -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);
|
||||
|
||||
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) {
|
||||
// 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) {
|
||||
let realmId = this[realmKey];
|
||||
let realmId = this[keys.realm];
|
||||
|
||||
if (!realmId) {
|
||||
throw new TypeError('write method was not called on a Realm object!');
|
||||
|
@ -59,34 +95,36 @@ 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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[
|
||||
util.createMethods(Realm.prototype, objectTypes.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});
|
||||
|
||||
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;
|
||||
|
|
|
@ -1,32 +1,18 @@
|
|||
'use strict';
|
||||
|
||||
let rpc = require('./rpc');
|
||||
let constants = require('./constants');
|
||||
let util = require('./util');
|
||||
|
||||
let idKey = util.idKey;
|
||||
let realmKey = util.realmKey;
|
||||
let resizeListKey = util.resizeListKey;
|
||||
module.exports = {
|
||||
create,
|
||||
};
|
||||
|
||||
exports.create = create;
|
||||
class Results {}
|
||||
|
||||
util.createMethods(Results.prototype, constants.objectTypes.RESULTS, [
|
||||
'sortByProperty',
|
||||
]);
|
||||
|
||||
function create(realmId, info) {
|
||||
let results = util.createList(null, getterForLength, getterForIndex);
|
||||
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);
|
||||
};
|
||||
return util.createList(Results.prototype, realmId, info);
|
||||
}
|
||||
|
|
102
lib/rpc.js
102
lib/rpc.js
|
@ -1,11 +1,10 @@
|
|||
'use strict';
|
||||
|
||||
let util = require('./util');
|
||||
let constants = require('./constants');
|
||||
|
||||
let DEVICE_HOST = 'localhost:8082';
|
||||
|
||||
let idKey = util.idKey;
|
||||
let realmKey = util.realmKey;
|
||||
let {keys, objectTypes, propTypes} = constants;
|
||||
let typeConverters = {};
|
||||
let XMLHttpRequest = window.XMLHttpRequest;
|
||||
|
||||
|
@ -17,32 +16,34 @@ if (XMLHttpRequest.__proto__ != window.XMLHttpRequestEventTarget) {
|
|||
window.XMLHttpRequest = override;
|
||||
}
|
||||
|
||||
exports.registerTypeConverter = registerTypeConverter;
|
||||
module.exports = {
|
||||
registerTypeConverter,
|
||||
|
||||
exports.createRealm = createRealm;
|
||||
exports.callRealmMethod = callRealmMethod;
|
||||
getDefaultPath,
|
||||
setDefaultPath,
|
||||
createRealm,
|
||||
callMethod,
|
||||
getProperty,
|
||||
setProperty,
|
||||
beginTransaction,
|
||||
cancelTransaction,
|
||||
commitTransaction,
|
||||
|
||||
exports.getObjectProperty = getObjectProperty;
|
||||
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;
|
||||
clearTestState,
|
||||
};
|
||||
|
||||
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));
|
||||
|
@ -51,54 +52,23 @@ function createRealm(args) {
|
|||
return sendRequest('create_realm', {arguments: args});
|
||||
}
|
||||
|
||||
function callRealmMethod(realmId, name, args) {
|
||||
function callMethod(realmId, id, 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, name, arguments: args});
|
||||
return deserialize(realmId, result);
|
||||
}
|
||||
|
||||
function getObjectProperty(realmId, objectId, name) {
|
||||
let result = sendRequest('get_property', {realmId, objectId, name});
|
||||
function getProperty(realmId, id, name) {
|
||||
let result = sendRequest('get_property', {realmId, id, name});
|
||||
return deserialize(realmId, result);
|
||||
}
|
||||
|
||||
function setObjectProperty(realmId, objectId, name, value) {
|
||||
function setProperty(realmId, id, name, value) {
|
||||
value = serialize(realmId, value);
|
||||
sendRequest('set_property', {realmId, objectId, 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});
|
||||
sendRequest('set_property', {realmId, id, name, value});
|
||||
}
|
||||
|
||||
function beginTransaction(realmId) {
|
||||
|
@ -118,17 +88,25 @@ function clearTestState() {
|
|||
}
|
||||
|
||||
function serialize(realmId, value) {
|
||||
if (typeof value == 'function') {
|
||||
return {type: objectTypes.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');
|
||||
}
|
||||
|
||||
return {id: id};
|
||||
return {id};
|
||||
}
|
||||
|
||||
if (value instanceof Date) {
|
||||
return {type: propTypes.DATE, value: value.getTime()};
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
|
|
19
lib/types.js
19
lib/types.js
|
@ -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);
|
86
lib/util.js
86
lib/util.js
|
@ -1,20 +1,27 @@
|
|||
'use strict';
|
||||
|
||||
let idKey = exports.idKey = Symbol();
|
||||
let realmKey = exports.realmKey = Symbol();
|
||||
let resizeListKey = exports.resizeListKey = Symbol();
|
||||
let constants = require('./constants');
|
||||
let rpc = require('./rpc');
|
||||
|
||||
exports.createList = createList;
|
||||
let {keys} = constants;
|
||||
|
||||
function createList(prototype, getterForLength, getterForIndex, setterForIndex) {
|
||||
var list = prototype ? Object.create(prototype) : {};
|
||||
var size = 0;
|
||||
module.exports = {
|
||||
createList,
|
||||
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) {
|
||||
length = this.length;
|
||||
length = list.length;
|
||||
}
|
||||
if (length == size) {
|
||||
return;
|
||||
|
@ -25,23 +32,74 @@ function createList(prototype, getterForLength, getterForIndex, setterForIndex)
|
|||
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
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: 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);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -214,7 +214,7 @@ JSObjectRef RJSListCreate(JSContextRef ctx, realm::List &list) {
|
|||
return RJSWrapObject<List *>(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},
|
||||
|
|
|
@ -20,9 +20,5 @@
|
|||
#import "shared_realm.hpp"
|
||||
#import "list.hpp"
|
||||
|
||||
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);
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -143,21 +143,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);
|
||||
}
|
||||
|
|
|
@ -24,8 +24,6 @@ namespace realm {
|
|||
using ObjectDefaults = std::map<std::string, JSValueRef>;
|
||||
}
|
||||
|
||||
extern const JSStaticFunction RJSRealmFuncs[];
|
||||
|
||||
JSClassRef RJSRealmClass();
|
||||
JSClassRef RJSRealmConstructorClass();
|
||||
JSClassRef RJSNotificationClass();
|
||||
|
|
|
@ -466,7 +466,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},
|
||||
|
|
|
@ -26,6 +26,3 @@ namespace realm {
|
|||
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);
|
||||
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
|
||||
|
||||
JSClassRef RJSResultsClass() {
|
||||
const JSStaticFunction resultsFuncs[] = {
|
||||
static const JSStaticFunction RJSResultsFuncs[] = {
|
||||
{"sortByProperty", SortByProperty, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete},
|
||||
{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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 <realm.hpp>
|
||||
|
|
199
src/RealmRPC.mm
199
src/RealmRPC.mm
|
@ -19,6 +19,7 @@
|
|||
#import "RealmRPC.h"
|
||||
#import <JavaScriptCore/JavaScriptCore.h>
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include "RealmJS.h"
|
||||
|
@ -35,6 +36,10 @@
|
|||
using RPCObjectID = u_int64_t;
|
||||
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 {
|
||||
JSGlobalContextRef _context;
|
||||
std::map<std::string, RPCRequest> _requests;
|
||||
|
@ -55,9 +60,24 @@ using RPCRequest = std::function<NSDictionary *(NSDictionary *dictionary)>;
|
|||
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;
|
||||
|
||||
_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;
|
||||
|
@ -92,36 +112,50 @@ using RPCRequest = std::function<NSDictionary *(NSDictionary *dictionary)>;
|
|||
RJSGetInternal<realm::SharedRealm *>(_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"]
|
||||
args:dict[@"arguments"]
|
||||
objectId:[dict[@"realmId"] unsignedLongValue]];
|
||||
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[@"objectId"] unsignedLongValue];
|
||||
JSValueRef propertyValue = ObjectGetProperty(_context, _objects[objectId], propString, &exception);
|
||||
|
||||
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 realmId = [dict[@"objectId"] unsignedLongValue];
|
||||
RPCObjectID oid = [dict[@"id"] unsignedLongValue];
|
||||
id name = dict[@"name"];
|
||||
JSValueRef value = [self deserializeDictionaryValue:dict[@"value"]];
|
||||
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);
|
||||
}
|
||||
|
||||
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];
|
||||
|
@ -129,84 +163,6 @@ using RPCRequest = std::function<NSDictionary *(NSDictionary *dictionary)>;
|
|||
_objects.erase(oid);
|
||||
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) {
|
||||
for (auto object : _objects) {
|
||||
JSValueUnprotect(_context, object.second);
|
||||
|
@ -236,30 +192,25 @@ using RPCRequest = std::function<NSDictionary *(NSDictionary *dictionary)>;
|
|||
return response ?: @{};
|
||||
}
|
||||
|
||||
- (NSDictionary *)performObjectMethod:(const char *)name
|
||||
classMethods:(const JSStaticFunction [])methods
|
||||
args:(NSArray *)args
|
||||
objectId:(RPCObjectID)oid {
|
||||
- (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]];
|
||||
}
|
||||
|
||||
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);
|
||||
JSValueRef result = JSObjectCallAsFunction(_context, function, object, argCount, argValues, &exception);
|
||||
|
||||
if (exception) {
|
||||
return @{@"error": @(RJSStringForValue(_context, exception).c_str())};
|
||||
}
|
||||
return @{@"result": [self resultForJSValue:ret]};
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
return @{@"error": @"invalid method"};
|
||||
return @{@"result": [self resultForJSValue:result]};
|
||||
}
|
||||
|
||||
- (RPCObjectID)storeObject:(JSObjectRef)object {
|
||||
|
@ -311,12 +262,19 @@ using RPCRequest = std::function<NSDictionary *(NSDictionary *dictionary)>;
|
|||
realm::Results *results = RJSGetInternal<realm::Results *>(jsObject);
|
||||
RPCObjectID oid = [self storeObject:jsObject];
|
||||
return @{
|
||||
@"type": @"ObjectTypesRESULTS",
|
||||
@"type": @(RealmObjectTypesResults),
|
||||
@"id": @(oid),
|
||||
@"size": @(results->size()),
|
||||
@"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)) {
|
||||
size_t length = RJSValidatedListLength(_context, jsObject);
|
||||
NSMutableArray *array = [NSMutableArray new];
|
||||
|
@ -325,6 +283,12 @@ using RPCRequest = std::function<NSDictionary *(NSDictionary *dictionary)>;
|
|||
}
|
||||
return @{@"value": array};
|
||||
}
|
||||
else if (RJSIsValueDate(_context, value)) {
|
||||
return @{
|
||||
@"type": @(RJSTypeGet(realm::PropertyTypeDate).c_str()),
|
||||
@"value": @(RJSValidatedValueToNumber(_context, value)),
|
||||
};
|
||||
}
|
||||
else {
|
||||
assert(0);
|
||||
}
|
||||
|
@ -354,7 +318,28 @@ using RPCRequest = std::function<NSDictionary *(NSDictionary *dictionary)>;
|
|||
return _objects[oid];
|
||||
}
|
||||
|
||||
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("");
|
||||
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) {
|
||||
return JSValueMakeUndefined(_context);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue