diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 00000000..b2e4be37 --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1,7 @@ +;; Project specific Emacs settings +((nil . ((c-basic-offset . 4) + (indent-tabs-mode . nil) + (fill-column . 80) + (c-file-style . "ellemtel") + (c-file-offsets . ((innamespace . 0))) + (show-trailing-whitespace . t)))) diff --git a/.gitignore b/.gitignore index 5d675635..c585b641 100644 --- a/.gitignore +++ b/.gitignore @@ -98,6 +98,7 @@ build/ # node.js node_modules/ npm-debug.log +/compiled/ # Android/IJ /android/ diff --git a/.gitmodules b/.gitmodules index 1475d7b4..af4fc586 100644 --- a/.gitmodules +++ b/.gitmodules @@ -6,5 +6,5 @@ url = https://github.com/realm/realm-jsdoc.git [submodule "src/object-store"] path = src/object-store - url = https://github.com/realm/realm-object-store.git - branch = js + url = git@github.com:realm/realm-object-store-private.git + branch = js-sync diff --git a/README.md b/README.md index f918c851..4bc70c95 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Prerequisites: First clone this repository: ``` -git clone https://github.com/realm/realm-js.git +git clone https://github.com/realm/realm-js-private.git ``` Then in the cloned directory: @@ -60,6 +60,15 @@ To build for Android: - `./gradlew publishAndroid` - The compiled version of the Android module is here: `/android` +To build for Node: +- `REALM_CORE_PREFIX=/path/to/realm-core REALM_SYNC_PREFIX=/path/to/realm-sync npm install` + +The `REALM_*_PREFIX` paths need to be absolute. Make sure to run `sh build.sh build-node` in both core and sync. Optionally export `REALMJS_USE_DEBUG_CORE=true` to link against the debug version of the realm binaries. + +To build the Developer Edition of the module, pass `--developer_edition` to `npm install` or change the default value in `binding.gyp`. + +`node-pre-gyp` is used for packaging. Run `scripts/build-node-pre-gyp.sh` with the same arguments and environment variables as `npm install` on all platforms (e.g. Linux and Darwin). Then, edit `package.json` to remove the `--build-from-source` option from the install script and run `npm pack`. Lastly, either create a fat package by merging all the tarballs created so far into one, or upload the `node-pre-gyp` tarballs to the CDN the `binary` section of `package.json` points to. + ## Code of Conduct This project adheres to the Contributor Covenant [code of conduct](https://realm.io/conduct/). diff --git a/Realm.xcworkspace/xcshareddata/Realm.xcscmblueprint b/Realm.xcworkspace/xcshareddata/Realm.xcscmblueprint index 3a7fb82e..ab8ab04a 100644 --- a/Realm.xcworkspace/xcshareddata/Realm.xcscmblueprint +++ b/Realm.xcworkspace/xcshareddata/Realm.xcscmblueprint @@ -5,12 +5,14 @@ }, "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { "40F53A12E4AE40C654358321B91166ABD3E910A6" : 0, - "F6F96CA34C5878B0A9123C7C37855491A5E599DA" : 0 + "F6F96CA34C5878B0A9123C7C37855491A5E599DA" : 0, + "8F3C415DA79CDA7D23734F285B95F9F9A3C0CB81" : 0 }, "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "5EE721F9-041C-4877-9E73-A925C9DB080A", "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { "40F53A12E4AE40C654358321B91166ABD3E910A6" : "realm-js\/", - "F6F96CA34C5878B0A9123C7C37855491A5E599DA" : "realm-js\/vendor\/GCDWebServer\/" + "F6F96CA34C5878B0A9123C7C37855491A5E599DA" : "realm-js\/vendor\/GCDWebServer\/", + "8F3C415DA79CDA7D23734F285B95F9F9A3C0CB81" : "realm-js\/src\/object-store\/" }, "DVTSourceControlWorkspaceBlueprintNameKey" : "Realm", "DVTSourceControlWorkspaceBlueprintVersion" : 204, @@ -21,6 +23,11 @@ "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "40F53A12E4AE40C654358321B91166ABD3E910A6" }, + { + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/realm\/realm-object-store.git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "8F3C415DA79CDA7D23734F285B95F9F9A3C0CB81" + }, { "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/swisspol\/GCDWebServer.git", "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", diff --git a/binding.gyp b/binding.gyp new file mode 100644 index 00000000..9570c888 --- /dev/null +++ b/binding.gyp @@ -0,0 +1,105 @@ +{ + "includes": [ + "src/node/gyp/target_defaults.gypi", + "src/node/gyp/realm.gyp" + ], + "targets": [ + { + "variables": { + "developer_edition%": "0" + }, + "target_name": "realm", + "dependencies": [ + "object-store" + ], + "sources": [ + "src/node/node_sync_logger.cpp", + "src/node/node_init.cpp", + "src/node/platform.cpp", + "src/js_realm.cpp" + ], + "include_dirs": [ + "src" + ], + "defines": [ "REALM_DEVELOPER_EDITION=<(developer_edition)" ], + "link_settings": { + "ldflags": [ + "-Wl,--exclude-libs=ALL" + ] + }, + "xcode_settings": { + "OTHER_LDFLAGS": [ "-Xlinker -unexported_symbol -Xlinker '*'" ] + } + }, + { + "variables": { + "object-store-include-dirs": [ + "src/object-store/src", + "src/object-store/src/impl", + "src/object-store/src/impl/apple", + "src/object-store/src/parser", + "src/object-store/external/pegtl" + ] + }, + "target_name": "object-store", + "dependencies": [ "realm-sync" ], # sync also includes core + "type": "static_library", + "include_dirs": [ "<@(object-store-include-dirs)" ], + "sources": [ + "src/object-store/src/collection_notifications.cpp", + "src/object-store/src/index_set.cpp", + "src/object-store/src/list.cpp", + "src/object-store/src/object_schema.cpp", + "src/object-store/src/object_store.cpp", + "src/object-store/src/results.cpp", + "src/object-store/src/schema.cpp", + "src/object-store/src/shared_realm.cpp", + "src/object-store/src/sync_manager.cpp", + "src/object-store/src/sync_session.cpp", + "src/object-store/src/thread_confined.cpp", + "src/object-store/src/global_notifier.cpp", + "src/object-store/src/impl/collection_change_builder.cpp", + "src/object-store/src/impl/collection_notifier.cpp", + "src/object-store/src/impl/handover.cpp", + "src/object-store/src/impl/list_notifier.cpp", + "src/object-store/src/impl/realm_coordinator.cpp", + "src/object-store/src/impl/results_notifier.cpp", + "src/object-store/src/impl/transact_log_handler.cpp", + "src/object-store/src/impl/weak_realm_notifier.cpp", + "src/object-store/src/parser/parser.cpp", + "src/object-store/src/parser/query_builder.cpp", + "src/object-store/src/util/format.cpp", + "src/object-store/src/util/thread_id.cpp" + ], + "conditions": [ + ["OS=='linux'", { + "sources": [ + "src/object-store/src/impl/android/external_commit_helper.cpp", + ] + }], + ["OS=='mac'", { + "sources": [ + "src/object-store/src/impl/apple/external_commit_helper.cpp" + ] + }] + ], + "all_dependent_settings": { + "include_dirs": [ "<@(object-store-include-dirs)" ] + }, + "export_dependent_settings": [ + "<@(_dependencies)" # re-export settings related to linking the realm binaries + ] + }, + { + "target_name": "action_after_build", + "type": "none", + "dependencies": [ "<(module_name)" ], + "copies": [ + { + "files": [ "<(PRODUCT_DIR)/<(module_name).node" ], + "destination": "<(module_path)" + } + ] + } + ] +} diff --git a/docs/realm.js b/docs/realm.js index c721a571..87beb336 100644 --- a/docs/realm.js +++ b/docs/realm.js @@ -189,6 +189,10 @@ Realm.defaultPath; * object types in this Realm. **Required** when first creating a Realm at this `path`. * @property {number} [schemaVersion] - **Required** (and must be incremented) after * changing the `schema`. + * @property {Object} [sync] - Sync configuration parameters with the following + * child properties: + * - `user` - A `User` object obtained by calling `Realm.Sync.User.login` + * - `url` - A `string` which contains a valid Realm Sync url */ /** diff --git a/docs/sync.js b/docs/sync.js new file mode 100644 index 00000000..bb113d40 --- /dev/null +++ b/docs/sync.js @@ -0,0 +1,93 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +/** + * @memberof Realm + */ +class Sync { +} + +/** + * Set a global listener function. + * @param {string} local_path - The path to the directory where realm files are stored [deprecated] + * @param {string} server_url - The sync server to listen to + * @param {SyncUser} admin_user - An admin user obtained by calling `new Realm.Sync.User.Admin` + * @param {function(realm_name)} filter_callback - Return true to recieve changes for the given realm + * @param {function(realm_name, realm, change_set)} change_callback - called on any realm changes with + * the following arguments: + * - `realm_name` - path of the Realm on which changes occurred + * - `realm` - a `Realm` object for the changed Realm + * - `change_set` - a dictionary of object names to arays of indexes indicating the indexes of objects of each type + * which have been added, removed, or modified + */ +Sync.setGlobalListener = function(local_path, server_url, admin_user, filter_callback, change_callback) {}; + +/** + * Set the sync log level. + * @param {string} log_level + */ +Sync.setLogLevel = function(log_level) {}; + +/** + * @typedef Realm.Sync~LogLevel + * @type {("error"|"info"|"debug")} + */ + +/** + * Class for logging in and managing Sync users. + * @memberof Realm.Sync + */ +class User { + /** + * Login a sync user with username and password. + * @param {string} server - authentication server + * @param {string} username + * @param {string} password + * @param {function(error, user)} callback - called with the following arguments: + * - `error` - an Error object is provided on failure + * - `user` - a valid User object on success + */ + login(server, username, password, callback) {} + /** + * Login a sync user using an external login provider. + * @param {string} server - authentication server + * @param {string} provider - The provider type + * @param {string} providerToken - The access token for the given provider + * @param {function(error, User)} callback - called with the following arguments: + * - `error` - an Error object is provided on failure + * - `user` - a valid User object on success + */ + loginWithProvider(server, provider, providerToken, callback) {} + /** + * Create a new user using the username/password provider + * @param {string} server - authentication server + * @param {string} username + * @param {string} password + * @param {function(error, User)} callback - called with the following arguments: + * - `error` - an Error object is provided on failure + * - `user` - a valid User object on success + */ + create(server, username, password, callback) {} + /** + * Create an admin user for the given authentication server with an existing token + * @param {string} server - authentication server + * @param {string} adminToken - existing admin token + * @return {User} - admin user populated with the given token and server + */ + adminUser(server, adminToken) {} +} diff --git a/examples/ReactExample/components/realm.js b/examples/ReactExample/components/realm.js index 13d042ad..6e71639a 100644 --- a/examples/ReactExample/components/realm.js +++ b/examples/ReactExample/components/realm.js @@ -34,6 +34,7 @@ TodoList.schema = { name: 'TodoList', properties: { name: 'string', + creationDate: 'date', items: {type: 'list', objectType: 'Todo'}, }, }; diff --git a/examples/ReactExample/components/todo-app.js b/examples/ReactExample/components/todo-app.js index c0d03f70..d2ff4fe4 100644 --- a/examples/ReactExample/components/todo-app.js +++ b/examples/ReactExample/components/todo-app.js @@ -38,15 +38,18 @@ export default class TodoApp extends React.Component { constructor(props) { super(props); - let todoLists = realm.objects('TodoList'); - if (todoLists.length < 1) { + // This is a Results object, which will live-update. + this.todoLists = realm.objects('TodoList').sorted('creationDate'); + if (this.todoLists.length < 1) { realm.write(() => { - realm.create('TodoList', {name: 'Todo List'}); + realm.create('TodoList', {name: 'Todo List', creationDate: new Date()}); }); } + this.todoLists.addListener((name, changes) => { + console.log("changed: " + JSON.stringify(changes)); + }); + console.log("registered listener"); - // This is a Results object, which will live-update. - this.todoLists = todoLists; // Bind all the methods that we will be passing as props. this.renderScene = this.renderScene.bind(this); @@ -79,7 +82,6 @@ export default class TodoApp extends React.Component { component: TodoListView, passProps: { ref: 'listView', - items: this.todoLists, extraItems: extraItems, onPressItem: this._onPressTodoList, }, @@ -105,7 +107,8 @@ export default class TodoApp extends React.Component { } renderScene(route) { - return + console.log(this.todoLists); + return } _addNewTodoItem(list) { @@ -128,7 +131,7 @@ export default class TodoApp extends React.Component { } realm.write(() => { - realm.create('TodoList', {name: ''}); + realm.create('TodoList', {name: '', creationDate: new Date()}); }); this._setEditingRow(items.length - 1); diff --git a/examples/sync.js b/examples/sync.js new file mode 100644 index 00000000..6ddbf945 --- /dev/null +++ b/examples/sync.js @@ -0,0 +1,46 @@ +'use strict'; + +var Realm = require('..'); + +var filename = "sync.realm"; +var syncUrl = "realm://127.0.0.1/nodejs/sync.realm"; +var syncUserToken = "eyJpZGVudGl0eSI6Im5vZGVqcy1kZW1vIn0K"; + +function Foo() {} +Foo.schema = { + name: 'Foo', + properties: { + name: Realm.Types.STRING, + number: Realm.Types.INT, + } +}; + +var realm = new Realm({ + path: filename, + syncUrl: syncUrl, + syncUserToken: syncUserToken, + schema: [Foo] +}); + +console.log('Starting...'); + +var prompt = require('prompt'); +prompt.start(); + +var run = true; +var my_prompt = function() { + prompt.get(['command'], function (err, result) { + if (err) { return onErr(err); } + console.log('Command: ' + result.command); + if (result.command == 'exit') { + run = false; + } + + if (run) { + my_prompt(); + } + }); +}; + +my_prompt(); + diff --git a/lib/index.js b/lib/index.js index 6f91fa5b..69643e20 100644 --- a/lib/index.js +++ b/lib/index.js @@ -18,7 +18,10 @@ 'use strict'; -var arrayMethods = require('./collection-methods'); +function node_require(module) { + return require(module); +} + var realmConstructor; if (typeof Realm != 'undefined') { @@ -31,14 +34,19 @@ if (typeof Realm != 'undefined') { // eslint-disable-next-line } else if (typeof process == 'object' && (('' + process) == '[object process]' || typeof jest == 'object')) { // Prevent React Native packager from seeing this module. - var bindings = 'bindings'; - realmConstructor = require(bindings)('realm').Realm; + var binary = node_require('node-pre-gyp'); + var path = node_require('path'); + var binding_path = binary.find(path.resolve(path.join(__dirname,'../package.json'))); + realmConstructor = require(binding_path).Realm; } else { throw new Error('Missing Realm constructor - please ensure RealmReact framework is included!'); } // Add the specified Array methods to the Collection prototype. -Object.defineProperties(realmConstructor.Collection.prototype, arrayMethods); +Object.defineProperties(realmConstructor.Collection.prototype, require('./collection-methods')); + +// Add sync methods +realmConstructor.Sync.User = require('./sync').User; // TODO: Remove this now useless object. var types = Object.freeze({ diff --git a/lib/sync.js b/lib/sync.js new file mode 100644 index 00000000..2cd0c5aa --- /dev/null +++ b/lib/sync.js @@ -0,0 +1,143 @@ +'use strict'; + +function node_require(module) { + return require(module); +} + +var post; +if (typeof fetch != 'undefined') { + post = function(options, callback) { + options.method = 'POST'; + fetch(options.url, options) + .then((response) => { + if (response.status != 200) { + callback(undefined, {statusCode: response.status}); + } + else { + return response.text(); + } + }) + .then((body) => { + callback(undefined, {statusCode: 200}, body) + }) + .catch((error) => { + callback(error); + }); + } +} +else { + post = node_require('request').post; +} + +const url = require("url"); + +const postHeaders = { + 'content-type': 'application/json;charset=utf-8', + 'accept': 'application/json' +}; + +function _authenticate(server, json, callback) { + json.app_id = ''; + var options = { + url: server + 'auth', + body: JSON.stringify(json), + headers: postHeaders + }; + post(options, function(error, response, body) { + if (error) { + console.log(error); + callback(error); + } + else if (response.statusCode != 200) { + console.log('Bad response: ' + response.statusCode); + callback(new Error('Bad response: ' + response.statusCode)); + } + else { + var rjson = JSON.parse(body); + // TODO: validate JSON + + const token = rjson.refresh_token.token; + const identity = rjson.refresh_token.token_data.identity; + callback(undefined, new User(server, identity, token)); + } + }); +} + +function User(server, identity, token) { + this.server = server; + this.identity = identity; + this.token = token; + this.isAdmin = false; + + User.activeUsers[identity] = this; +} + +User.adminUser = function(server, token) { + var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); + return v.toString(16); + }); + var user = new User(server, uuid, token); + user.isAdmin = true; + return user; +} + +User.activeUsers = {}; + +User.login = function(server, username, password, callback) { + _authenticate(server, { + provider: 'password', + user_info: { password: password }, + data: username + }, callback); +} + +User.loginWithProvider = function(server, provider, providerToken, callback) { + _authenticate(server, { + provider: provider, + data: providerToken + }, callback); +} + +User.create = function(server, username, password, callback) { + _authenticate(server, { + provider: 'password', + user_info: { password: password, register: true }, + data: username + }, callback); +} + +User.authenticateRealm = function(fileUrl, realmUrl, callback) { + var options = { + url: this.server + 'auth', + body: JSON.stringify({ + data: this.token, + path: url.parse(realmUrl).path, + provider: 'realm', + app_id: '' + }), + headers: postHeaders + }; + post(options, function(error, response, body) { + if (error) { + console.log(error); + callback(error); + } + else if (response.statusCode != 200) { + console.log('Bad response: ' + response.statusCode + body); + callback(new Error('Bad response: ' + response.statusCode)); + } + else { + var json = JSON.parse(body); + // TODO: validate JSON + + callback(undefined, { + token: json.access_token.token, + file_url: url.parse(fileUrl).path, + resolved_realm_url: 'realm://' + url.parse(realmUrl).host + json.access_token.token_data.path + }); + } + }); +} + +exports['User'] = User; diff --git a/package.json b/package.json index f9e97acd..4cc27420 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "realm", "description": "Realm is a mobile database: an alternative to SQLite and key-value stores", - "version": "0.14.3", + "version": "0.14.3-6", "license": "Apache-2.0", "homepage": "https://realm.io", "keywords": [ @@ -35,7 +35,8 @@ "react-native", "scripts", "src", - "vendor" + "vendor", + "binding.gyp" ], "scripts": { "get-version": "echo $npm_package_version", @@ -44,20 +45,20 @@ "jsdoc": "rm -rf docs/output && jsdoc -c docs/conf.json", "lint": "eslint", "test": "scripts/test.sh", - "prepublish": "scripts/prepublish.sh" + "install": "node-pre-gyp install --build-from-source" }, "dependencies": { - "bindings": "^1.2.1", "nan": "^2.3.3", - "node-gyp": "^3.3.1", - "sync-request": "^3.0.1" + "node-pre-gyp": "^0.6.30", + "request": "^2.74.0", + "sync-request": "^3.0.1", + "url": "^0.11.0" }, "devDependencies": { "babel-eslint": "^6.0.4", "eslint": "^2.10.2", "eslint-plugin-react": "^5.1.1", "jsdoc": "^3.4.0", - "mockery": "^1.7.0", "semver": "^5.1.0" }, "rnpm": { @@ -71,5 +72,11 @@ }, "engines": { "node": ">=4" + }, + "binary": { + "module_name": "realm", + "module_path": "./compiled/{node_abi}_{platform}_{arch}/", + "host": "https://static.realm.io", + "remote_path": "/node-pre-gyp" } } diff --git a/scripts/build-node-pre-gyp.sh b/scripts/build-node-pre-gyp.sh new file mode 100755 index 00000000..569f2acb --- /dev/null +++ b/scripts/build-node-pre-gyp.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +node_versions=${@:-4.4.7 5.12.0 6.5.0} + +topdir=$(cd $(dirname "$0")/..; pwd) + +die() { + echo $1 + exit 1 +} + +. ${topdir}/dependencies.list + +mkdir -p ${topdir}/out +: ${NVM_DIR=$topdir/.nvm} + +if [ ! -d "$NVM_DIR" ]; then + ( + git clone https://github.com/creationix/nvm.git "$NVM_DIR" + cd "$NVM_DIR" + git checkout `git describe --abbrev=0 --tags --match "v[0-9]*" origin` + ) +fi + +if [ -f "$NVM_DIR/nvm.sh" ]; then + . "$NVM_DIR/nvm.sh" +else + # we must be on mac and nvm was installed with brew + # TODO: change the mac slaves to use manual nvm installation + . "$(brew --prefix nvm)/nvm.sh" +fi + +for node_version in ${node_versions}; do + ( + rm -rf node_modules build + + nvm install ${node_version} || die "Could not install nodejs v${node_version}" + nvm use ${node_version} || die "Could not load nodejs v${node_version}" + + npm install "$EXTRA_NPM_ARGUMENTS" || die "Could not build module" + #./scripts/test.sh node || die "Unit tests for nodejs v${node_version} failed" + ./node_modules/.bin/node-pre-gyp package || die "Could not package module" + cp build/stage/node-pre-gyp/*.tar.gz ${topdir}/out/ + ) +done + diff --git a/scripts/download-core.sh b/scripts/download-core.sh index 2adf4b57..4723a701 100755 --- a/scripts/download-core.sh +++ b/scripts/download-core.sh @@ -4,7 +4,7 @@ set -e set -o pipefail # Set to "latest" for the latest build. -: ${REALM_CORE_VERSION:=1.5.0} +: ${REALM_CORE_VERSION:=2.0.0-rc4} if [ "$1" = '--version' ]; then echo "$REALM_CORE_VERSION" diff --git a/scripts/test.sh b/scripts/test.sh index c6f74f3f..6fffa740 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -154,21 +154,16 @@ case "$TARGET" in cat tests.xml ;; "node") - npm install - scripts/download-core.sh node - src/node/build-node.sh $CONFIGURATION - # Change to a temp directory. cd "$(mktemp -q -d -t realm.node.XXXXXX)" trap "rm -rf '$PWD'" EXIT - node "$SRCROOT/tests" + pushd "$SRCROOT/tests" + npm install + npm test + popd ;; "test-runners") - npm install - scripts/download-core.sh node - src/node/build-node.sh $CONFIGURATION - for runner in ava mocha jest; do pushd "$SRCROOT/tests/test-runners/$runner" npm install @@ -181,6 +176,27 @@ case "$TARGET" in cmake -DCMAKE_BUILD_TYPE=$CONFIGURATION . make run-tests ;; +"download-object-server") + . dependencies.list + + object_server_bundle="realm-object-server-bundled_node_darwin-$REALM_OBJECT_SERVER_VERSION.tar.gz" + curl -f -L "https://static.realm.io/downloads/object-server/$object_server_bundle" -o "$object_server_bundle" + rm -rf tests/sync-bundle + mkdir -p tests/sync-bundle + tar -C tests/sync-bundle -xf "$object_server_bundle" + rm "$object_server_bundle" + + echo -e "enterprise:\n skip_setup: true\n" >> "tests/sync-bundle/object-server/configuration.yml" + touch "tests/sync-bundle/object-server/do_not_open_browser" + ;; +"object-server-integration") + echo -e "yes\n" | ./tests/sync-bundle/reset-server-realms.command + + pushd "$SRCROOT/tests" + npm install + npm run test-sync + popd + ;; *) echo "Invalid target '${TARGET}'" exit 1 diff --git a/src/RealmJS.xcodeproj/project.pbxproj b/src/RealmJS.xcodeproj/project.pbxproj index 0ccdd01c..0f025d37 100644 --- a/src/RealmJS.xcodeproj/project.pbxproj +++ b/src/RealmJS.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 020229861D9EEB39000F0C4F /* global_notifier.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 020229661D9DBF02000F0C4F /* global_notifier.cpp */; }; + 020229871D9EEB39000F0C4F /* sync_metadata.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 020229801D9EEAF2000F0C4F /* sync_metadata.cpp */; }; + 020229881D9EEB39000F0C4F /* sync_session.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 020229821D9EEAF2000F0C4F /* sync_session.cpp */; }; 02409DC21BCF11D6005F3B3E /* RealmJSCoreTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 02409DC11BCF11D6005F3B3E /* RealmJSCoreTests.m */; }; 02414B881CE68CA200A8669F /* dates-v5.realm in Resources */ = {isa = PBXBuildFile; fileRef = 02414B871CE68CA200A8669F /* dates-v5.realm */; }; 02414BA51CE6ABCF00A8669F /* collection_change_builder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02414B991CE6AAEF00A8669F /* collection_change_builder.cpp */; }; @@ -14,6 +17,8 @@ 02414BA71CE6ABCF00A8669F /* list_notifier.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02414B9D1CE6AAEF00A8669F /* list_notifier.cpp */; }; 02414BA81CE6ABCF00A8669F /* results_notifier.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02414B9F1CE6AAEF00A8669F /* results_notifier.cpp */; }; 02414BA91CE6ABCF00A8669F /* collection_notifications.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02414B961CE6AADD00A8669F /* collection_notifications.cpp */; }; + 02443E391D8AF936007B0DF4 /* sync_manager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02443E361D8AF8FB007B0DF4 /* sync_manager.cpp */; }; + 02443E421D8AFB74007B0DF4 /* weak_realm_notifier.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02443E401D8AFB5F007B0DF4 /* weak_realm_notifier.cpp */; }; 0270BC821B7D020100010E03 /* RealmJSTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0270BC7B1B7D020100010E03 /* RealmJSTests.mm */; }; 027A23131CD3E379000543AE /* libRealmJS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F63FF2B11C1241E500B3B8E0 /* libRealmJS.a */; }; 02D041F71CE11159000E4250 /* dates-v3.realm in Resources */ = {isa = PBXBuildFile; fileRef = 02D041F61CE11159000E4250 /* dates-v3.realm */; }; @@ -29,9 +34,9 @@ 02F59ECA1C88F190007F774C /* parser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02F59EC61C88F190007F774C /* parser.cpp */; }; 02F59ECB1C88F190007F774C /* query_builder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02F59EC81C88F190007F774C /* query_builder.cpp */; }; 02F59ED41C88F1B6007F774C /* external_commit_helper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02F59ECF1C88F1B6007F774C /* external_commit_helper.cpp */; }; - 02F59ED51C88F1B6007F774C /* weak_realm_notifier.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02F59ED11C88F1B6007F774C /* weak_realm_notifier.cpp */; }; 02F59EE21C88F2BB007F774C /* realm_coordinator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02F59EDB1C88F2BA007F774C /* realm_coordinator.cpp */; }; 02F59EE31C88F2BB007F774C /* transact_log_handler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02F59EDD1C88F2BB007F774C /* transact_log_handler.cpp */; }; + 5D25F5A11D6284FD00EBBB30 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = F63FF3301C16434400B3B8E0 /* libz.tbd */; }; 5DC74A781D623C9800D77A4F /* handover.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5DC74A751D623C8700D77A4F /* handover.cpp */; }; 5DC74A791D623CA200D77A4F /* handover.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5DC74A751D623C8700D77A4F /* handover.cpp */; }; 5DC74A7A1D623CA800D77A4F /* thread_confined.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5DC74A721D623C7A00D77A4F /* thread_confined.cpp */; }; @@ -53,7 +58,6 @@ F61378791C18EAC5008BFC51 /* js in Resources */ = {isa = PBXBuildFile; fileRef = F61378781C18EAAC008BFC51 /* js */; }; F620F0581CB766DA0082977B /* node_init.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F620F0571CB766DA0082977B /* node_init.cpp */; }; F620F0751CB9F60C0082977B /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F620F0741CB9F60C0082977B /* CoreFoundation.framework */; }; - F63117F01CEB0D5F00ECB2DE /* weak_realm_notifier.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F63117EE1CEB0D5900ECB2DE /* weak_realm_notifier.cpp */; }; F63FF2C61C12469E00B3B8E0 /* jsc_init.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 029048011C0428DF00ABDED4 /* jsc_init.cpp */; }; F63FF2C91C12469E00B3B8E0 /* js_realm.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 029048071C0428DF00ABDED4 /* js_realm.cpp */; }; F63FF2CD1C12469E00B3B8E0 /* rpc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0290480F1C0428DF00ABDED4 /* rpc.cpp */; }; @@ -98,6 +102,12 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 020229661D9DBF02000F0C4F /* global_notifier.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = global_notifier.cpp; path = src/global_notifier.cpp; sourceTree = ""; }; + 020229671D9DBF02000F0C4F /* global_notifier.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = global_notifier.hpp; path = src/global_notifier.hpp; sourceTree = ""; }; + 020229801D9EEAF2000F0C4F /* sync_metadata.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = sync_metadata.cpp; path = src/sync_metadata.cpp; sourceTree = ""; }; + 020229811D9EEAF2000F0C4F /* sync_metadata.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = sync_metadata.hpp; path = src/sync_metadata.hpp; sourceTree = ""; }; + 020229821D9EEAF2000F0C4F /* sync_session.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = sync_session.cpp; path = src/sync_session.cpp; sourceTree = ""; }; + 020229831D9EEAF2000F0C4F /* sync_session.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = sync_session.hpp; path = src/sync_session.hpp; sourceTree = ""; }; 02409DC11BCF11D6005F3B3E /* RealmJSCoreTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RealmJSCoreTests.m; path = ios/RealmJSCoreTests.m; sourceTree = ""; }; 02414B871CE68CA200A8669F /* dates-v5.realm */ = {isa = PBXFileReference; lastKnownFileType = file; path = "dates-v5.realm"; sourceTree = ""; }; 02414B961CE6AADD00A8669F /* collection_notifications.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = collection_notifications.cpp; path = src/collection_notifications.cpp; sourceTree = ""; }; @@ -110,6 +120,12 @@ 02414B9E1CE6AAEF00A8669F /* list_notifier.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = list_notifier.hpp; sourceTree = ""; }; 02414B9F1CE6AAEF00A8669F /* results_notifier.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = results_notifier.cpp; sourceTree = ""; }; 02414BA01CE6AAEF00A8669F /* results_notifier.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = results_notifier.hpp; sourceTree = ""; }; + 02443E351D8AF8FB007B0DF4 /* sync_config.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = sync_config.hpp; path = src/sync_config.hpp; sourceTree = ""; }; + 02443E361D8AF8FB007B0DF4 /* sync_manager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = sync_manager.cpp; path = src/sync_manager.cpp; sourceTree = ""; }; + 02443E371D8AF8FB007B0DF4 /* sync_manager.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = sync_manager.hpp; path = src/sync_manager.hpp; sourceTree = ""; }; + 02443E3A1D8AFAD1007B0DF4 /* sync_client.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = sync_client.hpp; sourceTree = ""; }; + 02443E401D8AFB5F007B0DF4 /* weak_realm_notifier.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = weak_realm_notifier.cpp; sourceTree = ""; }; + 0250D9C01D7647E00012C20C /* js_sync.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = js_sync.hpp; sourceTree = ""; }; 025678951CAB392000FB8501 /* jsc_types.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = jsc_types.hpp; sourceTree = ""; }; 0270BC5A1B7CFC1300010E03 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 0270BC781B7D020100010E03 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = ios/Info.plist; sourceTree = ""; }; @@ -127,6 +143,7 @@ 029048101C0428DF00ABDED4 /* rpc.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = rpc.hpp; sourceTree = ""; }; 029048351C042A3C00ABDED4 /* platform.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = platform.hpp; sourceTree = ""; }; 029048381C042A8F00ABDED4 /* platform.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = platform.mm; sourceTree = ""; }; + 0290934A1CEFA9170009769E /* js_observable.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = js_observable.hpp; sourceTree = ""; }; 02A3C7A41BC4341500B1A7BE /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; 02B58CBC1AE99CEC009B348C /* RealmJSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RealmJSTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 02B58CCD1AE99D4D009B348C /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; @@ -157,14 +174,12 @@ 02F59EC91C88F190007F774C /* query_builder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = query_builder.hpp; sourceTree = ""; }; 02F59ECF1C88F1B6007F774C /* external_commit_helper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = external_commit_helper.cpp; sourceTree = ""; }; 02F59ED01C88F1B6007F774C /* external_commit_helper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = external_commit_helper.hpp; sourceTree = ""; }; - 02F59ED11C88F1B6007F774C /* weak_realm_notifier.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = weak_realm_notifier.cpp; sourceTree = ""; }; - 02F59ED21C88F1B6007F774C /* weak_realm_notifier.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = weak_realm_notifier.hpp; sourceTree = ""; }; 02F59EDA1C88F2BA007F774C /* external_commit_helper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = external_commit_helper.hpp; sourceTree = ""; }; 02F59EDB1C88F2BA007F774C /* realm_coordinator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = realm_coordinator.cpp; sourceTree = ""; }; 02F59EDC1C88F2BB007F774C /* realm_coordinator.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = realm_coordinator.hpp; sourceTree = ""; }; 02F59EDD1C88F2BB007F774C /* transact_log_handler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = transact_log_handler.cpp; sourceTree = ""; }; 02F59EDE1C88F2BB007F774C /* transact_log_handler.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = transact_log_handler.hpp; sourceTree = ""; }; - 02F59EDF1C88F2BB007F774C /* weak_realm_notifier_base.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = weak_realm_notifier_base.hpp; sourceTree = ""; }; + 02F59EDF1C88F2BB007F774C /* weak_realm_notifier.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = weak_realm_notifier.hpp; sourceTree = ""; }; 02F59EE01C88F2BB007F774C /* weak_realm_notifier.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = weak_realm_notifier.hpp; sourceTree = ""; }; 5DC74A721D623C7A00D77A4F /* thread_confined.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = thread_confined.cpp; path = src/thread_confined.cpp; sourceTree = ""; }; 5DC74A731D623C7A00D77A4F /* thread_confined.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = thread_confined.hpp; path = src/thread_confined.hpp; sourceTree = ""; }; @@ -204,15 +219,10 @@ F6267BC91CADC30000AC36B1 /* js_util.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = js_util.hpp; sourceTree = ""; }; F6267BCA1CADC49200AC36B1 /* node_dummy.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = node_dummy.cpp; sourceTree = ""; }; F62BF8FB1CAC71780022BCDC /* libRealmNode.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libRealmNode.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; - F63117EE1CEB0D5900ECB2DE /* weak_realm_notifier.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = weak_realm_notifier.cpp; sourceTree = ""; }; - F63117EF1CEB0D5900ECB2DE /* weak_realm_notifier.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = weak_realm_notifier.hpp; sourceTree = ""; }; F63118431CEBA7B700ECB2DE /* external_commit_helper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = external_commit_helper.cpp; sourceTree = ""; }; F63118441CEBA7B700ECB2DE /* external_commit_helper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = external_commit_helper.hpp; sourceTree = ""; }; - F63118451CEBA7B700ECB2DE /* weak_realm_notifier.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = weak_realm_notifier.cpp; sourceTree = ""; }; - F63118461CEBA7B700ECB2DE /* weak_realm_notifier.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = weak_realm_notifier.hpp; sourceTree = ""; }; F631184A1CEBA7D800ECB2DE /* external_commit_helper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = external_commit_helper.cpp; sourceTree = ""; }; F631184B1CEBA7D800ECB2DE /* external_commit_helper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = external_commit_helper.hpp; sourceTree = ""; }; - F631184C1CEBA7D800ECB2DE /* weak_realm_notifier.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = weak_realm_notifier.hpp; sourceTree = ""; }; F63FF2B11C1241E500B3B8E0 /* libRealmJS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRealmJS.a; sourceTree = BUILT_PRODUCTS_DIR; }; F63FF2F01C16405C00B3B8E0 /* libGCDWebServers.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libGCDWebServers.a; sourceTree = BUILT_PRODUCTS_DIR; }; F63FF2FD1C1642BB00B3B8E0 /* GCDWebServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServer.h; sourceTree = ""; }; @@ -266,6 +276,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 5D25F5A11D6284FD00EBBB30 /* libz.tbd in Frameworks */, 027A23131CD3E379000543AE /* libRealmJS.a in Frameworks */, 02D8D1F71B601984006DB49D /* JavaScriptCore.framework in Frameworks */, ); @@ -297,11 +308,13 @@ F6874A441CAD2ACD00EEEE36 /* JSC */, F62BF9001CAC72C40022BCDC /* Node */, F62A35141C18E783004A917D /* Object Store */, + 0290934A1CEFA9170009769E /* js_observable.hpp */, F60102F71CBDA6D400EC01BA /* js_collection.hpp */, 029048041C0428DF00ABDED4 /* js_list.hpp */, 029048061C0428DF00ABDED4 /* js_realm_object.hpp */, 029048071C0428DF00ABDED4 /* js_realm.cpp */, 029048081C0428DF00ABDED4 /* js_realm.hpp */, + 0250D9C01D7647E00012C20C /* js_sync.hpp */, 0290480A1C0428DF00ABDED4 /* js_results.hpp */, 0290480C1C0428DF00ABDED4 /* js_schema.hpp */, F620F0521CAF0B600082977B /* js_class.hpp */, @@ -414,6 +427,8 @@ 02F59EAE1C88F17D007F774C /* binding_context.hpp */, 02F59EAF1C88F17D007F774C /* index_set.cpp */, 02F59EB01C88F17D007F774C /* index_set.hpp */, + 020229661D9DBF02000F0C4F /* global_notifier.cpp */, + 020229671D9DBF02000F0C4F /* global_notifier.hpp */, 02F59EB11C88F17D007F774C /* list.cpp */, 02F59EB21C88F17D007F774C /* list.hpp */, 02F59EB31C88F17D007F774C /* object_accessor.hpp */, @@ -430,6 +445,13 @@ 02F59EBE1C88F17D007F774C /* shared_realm.hpp */, 5DC74A721D623C7A00D77A4F /* thread_confined.cpp */, 5DC74A731D623C7A00D77A4F /* thread_confined.hpp */, + 02443E351D8AF8FB007B0DF4 /* sync_config.hpp */, + 02443E361D8AF8FB007B0DF4 /* sync_manager.cpp */, + 02443E371D8AF8FB007B0DF4 /* sync_manager.hpp */, + 020229801D9EEAF2000F0C4F /* sync_metadata.cpp */, + 020229811D9EEAF2000F0C4F /* sync_metadata.hpp */, + 020229821D9EEAF2000F0C4F /* sync_session.cpp */, + 020229831D9EEAF2000F0C4F /* sync_session.hpp */, ); name = "Object Store"; path = "object-store"; @@ -464,7 +486,6 @@ F63118421CEBA7A100ECB2DE /* android */, F63117EB1CEB0C1B00ECB2DE /* apple */, F63118491CEBA7BD00ECB2DE /* generic */, - F63117ED1CEB0CC600ECB2DE /* node */, 02414B991CE6AAEF00A8669F /* collection_change_builder.cpp */, 02414B9A1CE6AAEF00A8669F /* collection_change_builder.hpp */, 02414B9B1CE6AAEF00A8669F /* collection_notifier.cpp */, @@ -478,9 +499,11 @@ 02F59EDA1C88F2BA007F774C /* external_commit_helper.hpp */, 02F59EDB1C88F2BA007F774C /* realm_coordinator.cpp */, 02F59EDC1C88F2BB007F774C /* realm_coordinator.hpp */, + 02443E3A1D8AFAD1007B0DF4 /* sync_client.hpp */, 02F59EDD1C88F2BB007F774C /* transact_log_handler.cpp */, 02F59EDE1C88F2BB007F774C /* transact_log_handler.hpp */, - 02F59EDF1C88F2BB007F774C /* weak_realm_notifier_base.hpp */, + 02F59EDF1C88F2BB007F774C /* weak_realm_notifier.hpp */, + 02443E401D8AFB5F007B0DF4 /* weak_realm_notifier.cpp */, 02F59EE01C88F2BB007F774C /* weak_realm_notifier.hpp */, ); name = impl; @@ -492,8 +515,6 @@ children = ( 02F59ECF1C88F1B6007F774C /* external_commit_helper.cpp */, 02F59ED01C88F1B6007F774C /* external_commit_helper.hpp */, - 02F59ED11C88F1B6007F774C /* weak_realm_notifier.cpp */, - 02F59ED21C88F1B6007F774C /* weak_realm_notifier.hpp */, ); path = apple; sourceTree = ""; @@ -510,22 +531,11 @@ path = src/parser; sourceTree = ""; }; - F63117ED1CEB0CC600ECB2DE /* node */ = { - isa = PBXGroup; - children = ( - F63117EE1CEB0D5900ECB2DE /* weak_realm_notifier.cpp */, - F63117EF1CEB0D5900ECB2DE /* weak_realm_notifier.hpp */, - ); - path = node; - sourceTree = ""; - }; F63118421CEBA7A100ECB2DE /* android */ = { isa = PBXGroup; children = ( F63118431CEBA7B700ECB2DE /* external_commit_helper.cpp */, F63118441CEBA7B700ECB2DE /* external_commit_helper.hpp */, - F63118451CEBA7B700ECB2DE /* weak_realm_notifier.cpp */, - F63118461CEBA7B700ECB2DE /* weak_realm_notifier.hpp */, ); path = android; sourceTree = ""; @@ -535,7 +545,6 @@ children = ( F631184A1CEBA7D800ECB2DE /* external_commit_helper.cpp */, F631184B1CEBA7D800ECB2DE /* external_commit_helper.hpp */, - F631184C1CEBA7D800ECB2DE /* weak_realm_notifier.hpp */, ); path = generic; sourceTree = ""; @@ -849,7 +858,6 @@ F6E931BC1CFEAE340016AF14 /* collection_notifier.cpp in Sources */, F60102DC1CBB96C900EC01BA /* query_builder.cpp in Sources */, F60102DD1CBB96CC00EC01BA /* external_commit_helper.cpp in Sources */, - F63117F01CEB0D5F00ECB2DE /* weak_realm_notifier.cpp in Sources */, F60102E11CBB96DD00EC01BA /* transact_log_handler.cpp in Sources */, F6E931BE1CFEAE3A0016AF14 /* results_notifier.cpp in Sources */, F60102D71CBB96B800EC01BA /* object_store.cpp in Sources */, @@ -870,6 +878,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 020229861D9EEB39000F0C4F /* global_notifier.cpp in Sources */, + 020229871D9EEB39000F0C4F /* sync_metadata.cpp in Sources */, + 020229881D9EEB39000F0C4F /* sync_session.cpp in Sources */, + 02443E421D8AFB74007B0DF4 /* weak_realm_notifier.cpp in Sources */, + 02443E391D8AF936007B0DF4 /* sync_manager.cpp in Sources */, 02E008D51D10ABB600F3AA37 /* format.cpp in Sources */, 5DC74A7A1D623CA800D77A4F /* thread_confined.cpp in Sources */, 02414BA51CE6ABCF00A8669F /* collection_change_builder.cpp in Sources */, @@ -887,7 +900,6 @@ 02F59ECA1C88F190007F774C /* parser.cpp in Sources */, 02F59EC01C88F17D007F774C /* list.cpp in Sources */, 02F59EBF1C88F17D007F774C /* index_set.cpp in Sources */, - 02F59ED51C88F1B6007F774C /* weak_realm_notifier.cpp in Sources */, F63FF2C91C12469E00B3B8E0 /* js_realm.cpp in Sources */, 02F59EC51C88F17D007F774C /* shared_realm.cpp in Sources */, 02F59ECB1C88F190007F774C /* query_builder.cpp in Sources */, @@ -1145,12 +1157,21 @@ isa = XCBuildConfiguration; buildSettings = { DEBUG_INFORMATION_FORMAT = dwarf; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + REALM_DEBUG, + REALM_HAVE_CONFIG, + __ASSERTMACROS__, + REALM_ENABLE_SYNC, + ); OTHER_CPLUSPLUSFLAGS = ( "$(inherited)", "-isystem", - ../core/include, + "../../realm-core/ios-lib/include", + "-isystem", + "../../realm-sync/src", ); - OTHER_LIBTOOLFLAGS = "$(SRCROOT)/../core/librealm-ios-dbg.a"; + OTHER_LIBTOOLFLAGS = "$(SRCROOT)/../../realm-core/ios-lib/librealm-ios-dbg.a $(SRCROOT)/../../realm-sync/src/realm/librealm-sync-iphonesimulator-dbg.a"; PRODUCT_NAME = RealmJS; SKIP_INSTALL = YES; }; @@ -1159,12 +1180,19 @@ F63FF2B91C1241E500B3B8E0 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + GCC_PREPROCESSOR_DEFINITIONS = ( + REALM_HAVE_CONFIG, + __ASSERTMACROS__, + REALM_ENABLE_SYNC, + ); OTHER_CPLUSPLUSFLAGS = ( "$(inherited)", "-isystem", - ../core/include, + "../../realm-core/ios-lib/include", + "-isystem", + "../../realm-sync/src", ); - OTHER_LIBTOOLFLAGS = "$(SRCROOT)/../core/librealm-ios.a"; + OTHER_LIBTOOLFLAGS = "$(SRCROOT)/../../realm-core/ios-lib/librealm-ios.a $(SRCROOT)/../../realm-sync/src/realm/librealm-sync-iphonesimulator.a"; PRODUCT_NAME = RealmJS; SKIP_INSTALL = YES; }; diff --git a/src/RealmJS.xcodeproj/xcshareddata/xcschemes/RealmNode.xcscheme b/src/RealmJS.xcodeproj/xcshareddata/xcschemes/RealmNode.xcscheme index 590746e9..200fcccc 100644 --- a/src/RealmJS.xcodeproj/xcshareddata/xcschemes/RealmNode.xcscheme +++ b/src/RealmJS.xcodeproj/xcshareddata/xcschemes/RealmNode.xcscheme @@ -5,22 +5,6 @@ - - - - - - - - - - - - + + + + + + @@ -71,15 +55,6 @@ savedToolIdentifier = "" useCustomWorkingDirectory = "NO" debugDocumentVersioning = "YES"> - - - - diff --git a/src/js_collection.hpp b/src/js_collection.hpp index 510333a7..9a758d18 100644 --- a/src/js_collection.hpp +++ b/src/js_collection.hpp @@ -19,6 +19,10 @@ #pragma once #include "js_class.hpp" +#include "js_types.hpp" +#include "js_observable.hpp" + +#include "collection_notifications.hpp" namespace realm { namespace js { @@ -27,9 +31,40 @@ namespace js { class Collection {}; template -struct CollectionClass : ClassDefinition { +struct CollectionClass : ClassDefinition> { + using ContextType = typename T::Context; + using ValueType = typename T::Value; + using ObjectType = typename T::Object; + using Object = js::Object; + using Value = js::Value; + std::string const name = "Collection"; + + static inline ValueType create_collection_change_set(ContextType ctx, const CollectionChangeSet &change_set); }; +template +typename T::Value CollectionClass::create_collection_change_set(ContextType ctx, const CollectionChangeSet &change_set) +{ + ObjectType object = Object::create_empty(ctx); + std::vector deletions, insertions, modifications; + for (auto index : change_set.deletions.as_indexes()) { + deletions.push_back(Value::from_number(ctx, index)); + } + Object::set_property(ctx, object, "deletions", Object::create_array(ctx, deletions)); + + for (auto index : change_set.insertions.as_indexes()) { + insertions.push_back(Value::from_number(ctx, index)); + } + Object::set_property(ctx, object, "insertions", Object::create_array(ctx, insertions)); + + for (auto index : change_set.modifications.as_indexes()) { + modifications.push_back(Value::from_number(ctx, index)); + } + Object::set_property(ctx, object, "modifications", Object::create_array(ctx, modifications)); + + return object; +} + } // js } // realm diff --git a/src/js_list.hpp b/src/js_list.hpp index 35c632e1..a730ad04 100644 --- a/src/js_list.hpp +++ b/src/js_list.hpp @@ -33,10 +33,20 @@ namespace realm { namespace js { template -struct ListClass : ClassDefinition> { +class List : public realm::List { + public: + List(std::shared_ptr r, const ObjectSchema& s, LinkViewRef l) noexcept : realm::List(r, l) {} + List(const realm::List &l) : realm::List(l) {} + + std::vector, NotificationToken>> m_notification_tokens; +}; + +template +struct ListClass : ClassDefinition, CollectionClass> { using ContextType = typename T::Context; using ObjectType = typename T::Object; using ValueType = typename T::Value; + using FunctionType = typename T::Function; using Object = js::Object; using Value = js::Value; using ReturnValue = js::ReturnValue; @@ -58,7 +68,12 @@ struct ListClass : ClassDefinition> { static void filtered(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); static void sorted(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); static void is_valid(ContextType, ObjectType, size_t, const ValueType [], ReturnValue &); - + + // observable + static void add_listener(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void remove_listener(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void remove_all_listeners(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + std::string const name = "List"; MethodMap const methods = { @@ -71,6 +86,9 @@ struct ListClass : ClassDefinition> { {"filtered", wrap}, {"sorted", wrap}, {"isValid", wrap}, + {"addListener", wrap}, + {"removeListener", wrap}, + {"removeAllListeners", wrap}, }; PropertyMap const properties = { @@ -82,7 +100,7 @@ struct ListClass : ClassDefinition> { template typename T::Object ListClass::create_instance(ContextType ctx, realm::List list) { - return create_object>(ctx, new realm::List(std::move(list))); + return create_object>(ctx, new realm::js::List(std::move(list))); } template @@ -230,6 +248,55 @@ template void ListClass::is_valid(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { return_value.set(get_internal>(this_object)->is_valid()); } + +template +void ListClass::add_listener(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count(argc, 1); + + auto list = get_internal>(this_object); + auto callback = Value::validated_to_function(ctx, arguments[0]); + Protected protected_callback(ctx, callback); + Protected protected_this(ctx, this_object); + Protected protected_ctx(Context::get_global_context(ctx)); + + auto token = list->add_notification_callback([=](CollectionChangeSet change_set, std::exception_ptr exception) { + typename T::HandleScope scope; + + ValueType arguments[2]; + arguments[0] = static_cast(protected_this); + arguments[1] = CollectionClass::create_collection_change_set(protected_ctx, change_set); + Function::call(protected_ctx, protected_callback, protected_this, 2, arguments); + }); + list->m_notification_tokens.emplace_back(protected_callback, std::move(token)); +} + +template +void ListClass::remove_listener(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count(argc, 1); + + auto list = get_internal>(this_object); + auto callback = Value::validated_to_function(ctx, arguments[0]); + auto protected_function = Protected(ctx, callback); + + auto iter = list->m_notification_tokens.begin(); + typename Protected::Comparator compare; + while (iter != list->m_notification_tokens.end()) { + if(compare(iter->first, protected_function)) { + iter = list->m_notification_tokens.erase(iter); + } + else { + iter++; + } + } +} + +template +void ListClass::remove_all_listeners(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count(argc, 0); + + auto list = get_internal>(this_object); + list->m_notification_tokens.clear(); +} } // js } // realm diff --git a/src/js_observable.hpp b/src/js_observable.hpp new file mode 100644 index 00000000..a49a1d0e --- /dev/null +++ b/src/js_observable.hpp @@ -0,0 +1,35 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "js_class.hpp" + +namespace realm { +namespace js { + +// Empty class that merely serves as useful type for now. +class Observable {}; + +template +struct ObservableClass : ClassDefinition { + std::string const name = "Observable"; +}; + +} // js +} // realm diff --git a/src/js_realm.hpp b/src/js_realm.hpp index 06821ac3..642b3b4a 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -28,11 +28,14 @@ #include "js_list.hpp" #include "js_results.hpp" #include "js_schema.hpp" +#include "js_observable.hpp" +#include "js_sync.hpp" #include "shared_realm.hpp" #include "binding_context.hpp" #include "object_accessor.hpp" #include "platform.hpp" +#include "sync_config.hpp" namespace realm { namespace js { @@ -99,8 +102,10 @@ class RealmDelegate : public BindingContext { Protected m_context; std::list> m_notifications; std::weak_ptr m_realm; - + void notify(const char *notification_name) { + typename T::HandleScope scope; + SharedRealm realm = m_realm.lock(); if (!realm) { throw std::runtime_error("Realm no longer exists"); @@ -124,7 +129,7 @@ void set_default_path(std::string path); void delete_all_realms(); template -class RealmClass : public ClassDefinition { +class RealmClass : public ClassDefinition> { using GlobalContextType = typename T::GlobalContext; using ContextType = typename T::Context; using FunctionType = typename T::Function; @@ -162,7 +167,7 @@ public: static void schema_version(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); static void clear_test_state(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); static void copy_bundled_realm_files(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); - + // static properties static void get_default_path(ContextType, ObjectType, ReturnValue &); static void set_default_path(ContextType, ObjectType, ValueType value); @@ -254,12 +259,14 @@ inline typename T::Function RealmClass::create_constructor(ContextType ctx) { FunctionType list_constructor = ObjectWrap>::create_constructor(ctx); FunctionType results_constructor = ObjectWrap>::create_constructor(ctx); FunctionType realm_object_constructor = ObjectWrap>::create_constructor(ctx); + FunctionType sync_constructor = SyncClass::create_constructor(ctx); PropertyAttributes attributes = ReadOnly | DontEnum | DontDelete; Object::set_property(ctx, realm_constructor, "Collection", collection_constructor, attributes); Object::set_property(ctx, realm_constructor, "List", list_constructor, attributes); Object::set_property(ctx, realm_constructor, "Results", results_constructor, attributes); Object::set_property(ctx, realm_constructor, "Object", realm_object_constructor, attributes); + Object::set_property(ctx, realm_constructor, "Sync", sync_constructor, attributes); return realm_constructor; } @@ -376,6 +383,8 @@ void RealmClass::constructor(ContextType ctx, ObjectType this_object, size_t std::string encryption_key = NativeAccessor::to_binary(ctx, encryption_key_value); config.encryption_key = std::vector(encryption_key.begin(), encryption_key.end()); } + + SyncClass::populate_sync_config(ctx, object, config); } } else { diff --git a/src/js_results.hpp b/src/js_results.hpp index 37d2cde9..2b8f58d4 100644 --- a/src/js_results.hpp +++ b/src/js_results.hpp @@ -31,10 +31,25 @@ namespace realm { namespace js { template -struct ResultsClass : ClassDefinition> { +class Results : public realm::Results { + public: + Results(Results const& r) : realm::Results(r) {}; + Results(realm::Results const& r) : realm::Results(r) {}; + Results(Results&&) = default; + Results& operator=(Results&&) = default; + Results& operator=(Results const&) = default; + + using realm::Results::Results; + + std::vector, NotificationToken>> m_notification_tokens; +}; + +template +struct ResultsClass : ClassDefinition, CollectionClass> { using ContextType = typename T::Context; using ObjectType = typename T::Object; using ValueType = typename T::Value; + using FunctionType = typename T::Function; using Object = js::Object; using Value = js::Value; using ReturnValue = js::ReturnValue; @@ -56,6 +71,11 @@ struct ResultsClass : ClassDefinition> { static void sorted(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); static void is_valid(ContextType, ObjectType, size_t, const ValueType [], ReturnValue &); + // observable + static void add_listener(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void remove_listener(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void remove_all_listeners(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + std::string const name = "Results"; MethodMap const methods = { @@ -63,6 +83,9 @@ struct ResultsClass : ClassDefinition> { {"filtered", wrap}, {"sorted", wrap}, {"isValid", wrap}, + {"addListener", wrap}, + {"removeListener", wrap}, + {"removeAllListeners", wrap}, }; PropertyMap const properties = { @@ -74,13 +97,13 @@ struct ResultsClass : ClassDefinition> { template typename T::Object ResultsClass::create_instance(ContextType ctx, realm::Results results) { - return create_object>(ctx, new realm::Results(std::move(results))); + return create_object>(ctx, new realm::js::Results(std::move(results))); } template typename T::Object ResultsClass::create_instance(ContextType ctx, SharedRealm realm, const ObjectSchema &object_schema) { auto table = ObjectStore::table_for_object_type(realm->read_group(), object_schema.name); - return create_object>(ctx, new realm::Results(realm, *table)); + return create_object>(ctx, new realm::js::Results(realm, *table)); } template @@ -155,8 +178,8 @@ typename T::Object ResultsClass::create_sorted(ContextType ctx, const U &coll } auto table = realm::ObjectStore::table_for_object_type(realm->read_group(), object_schema.name); - auto results = new realm::Results(realm, collection.get_query(), - {*table, std::move(columns), std::move(ascending)}); + auto results = new realm::js::Results(realm, collection.get_query(), + {*table, std::move(columns), std::move(ascending)}); return create_object>(ctx, results); } @@ -209,6 +232,55 @@ template void ResultsClass::is_valid(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { return_value.set(get_internal>(this_object)->is_valid()); } + +template +void ResultsClass::add_listener(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count(argc, 1); + + auto results = get_internal>(this_object); + auto callback = Value::validated_to_function(ctx, arguments[0]); + Protected protected_callback(ctx, callback); + Protected protected_this(ctx, this_object); + Protected protected_ctx(Context::get_global_context(ctx)); + + auto token = results->add_notification_callback([=](CollectionChangeSet change_set, std::exception_ptr exception) { + typename T::HandleScope scope; + ValueType arguments[2]; + arguments[0] = static_cast(protected_this); + arguments[1] = CollectionClass::create_collection_change_set(protected_ctx, change_set); + Function::call(protected_ctx, protected_callback, protected_this, 2, arguments); + }); + results->m_notification_tokens.emplace_back(protected_callback, std::move(token)); +} + +template +void ResultsClass::remove_listener(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count(argc, 1); + + auto results = get_internal>(this_object); + auto callback = Value::validated_to_function(ctx, arguments[0]); + auto protected_function = Protected(ctx, callback); + + auto iter = results->m_notification_tokens.begin(); + typename Protected::Comparator compare; + while (iter != results->m_notification_tokens.end()) { + if(compare(iter->first, protected_function)) { + iter = results->m_notification_tokens.erase(iter); + } + else { + iter++; + } + } +} + +template +void ResultsClass::remove_all_listeners(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count(argc, 0); + + auto results = get_internal>(this_object); + results->m_notification_tokens.clear(); +} + } // js } // realm diff --git a/src/js_sync.hpp b/src/js_sync.hpp new file mode 100644 index 00000000..cb560d1e --- /dev/null +++ b/src/js_sync.hpp @@ -0,0 +1,226 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include + +#include "js_class.hpp" +#include "js_collection.hpp" +#include "sync_manager.hpp" +#include "sync_config.hpp" +#include "sync_session.hpp" +#include "realm/util/logger.hpp" +#include "realm/util/uri.hpp" + +#if REALM_PLATFORM_NODE +#include "node/node_sync_logger.hpp" +#endif + +#if !REALM_DEVELOPER_EDITION +#include "js_enterprise.hpp" +#endif + +namespace realm { +namespace js { + +template +class SyncClass : public ClassDefinition { + using GlobalContextType = typename T::GlobalContext; + using ContextType = typename T::Context; + using FunctionType = typename T::Function; + using ObjectType = typename T::Object; + using ValueType = typename T::Value; + using String = js::String; + using Object = js::Object; + using Value = js::Value; + using Function = js::Function; + using ReturnValue = js::ReturnValue; + using NativeAccessor = realm::NativeAccessor; + +public: + std::string const name = "Sync"; + + static FunctionType create_constructor(ContextType); + + static void set_sync_log_level(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void set_verify_servers_ssl_certificate(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + +#if REALM_PLATFORM_NODE + static void set_sync_logger(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); +#endif + + // private + static void refresh_access_token(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void populate_sync_config(ContextType, ObjectType config_object, Realm::Config&); + + // static properties + static void get_is_developer_edition(ContextType, ObjectType, ReturnValue &); + + MethodMap const static_methods = { + {"refreshAccessToken", wrap}, + {"setLogLevel", wrap}, + {"setVerifyServersSslCertificate", wrap}, +#if REALM_PLATFORM_NODE + {"setSyncLogger", wrap}, +#endif + }; + + PropertyMap const static_properties { + {"isDeveloperEdition", {wrap, nullptr}} + }; +}; + +template +inline typename T::Function SyncClass::create_constructor(ContextType ctx) { + FunctionType sync_constructor = ObjectWrap>::create_constructor(ctx); + + Protected refresh(ctx, Object::validated_get_function(ctx, sync_constructor, std::string("refreshAccessToken"))); + Protected protected_sync(ctx, sync_constructor); + Protected protected_ctx(Context::get_global_context(ctx)); + + realm::SyncManager::shared().set_login_function([=](const std::string& path, const realm::SyncConfig& config) { + typename T::HandleScope handle_scope; + + FunctionType user_constructor = Object::validated_get_function(protected_ctx, protected_sync, std::string("User")); + FunctionType authenticate = Object::validated_get_function(protected_ctx, user_constructor, "authenticateRealm"); + ObjectType users = Object::validated_get_object(protected_ctx, user_constructor, std::string("activeUsers")); + ObjectType user = Object::validated_get_object(protected_ctx, users, config.user_tag.c_str(), "Invalid user identity"); + + if (Object::validated_get_boolean(protected_ctx, user, "isAdmin")) { + std::string token = Object::validated_get_string(protected_ctx, user, "token"); + + // FIXME: This log-in callback is called while the object store still holds some sync-related locks. + // Notify the object store of the access token asynchronously to avoid the deadlock that would result + // from reentering the object store here. + auto thread = std::thread([path, config, token]{ + auto session = SyncManager::shared().get_existing_active_session(path); + session->refresh_access_token(token, config.realm_url); + }); + thread.detach(); + } + else { + ValueType arguments[3]; + arguments[0] = Value::from_string(protected_ctx, path.c_str()); + arguments[1] = Value::from_string(protected_ctx, config.realm_url.c_str()); + arguments[2] = refresh; + Function::call(protected_ctx, authenticate, user, 3, arguments); + } + }); + + realm::SyncManager::shared().set_error_handler([=](int error_code, std::string message) { + std::cout << error_code << " " << message << std::endl; + }); + +#if REALM_PLATFORM_NODE + // AtExit is called before the V8 VM is disposed. We use it to clean the captured JS objects in the login function. + // Should they be destructed after the VM is disposed, there will be a segmentation fault during node's shutdown. + ::node::AtExit([](void*) { + realm::SyncManager::shared().set_login_function(nullptr); + }); +#endif + +#if !REALM_DEVELOPER_EDITION + SyncEnterpriseClass::add_methods(ctx, sync_constructor); +#endif + + return sync_constructor; +} + +template +void SyncClass::set_sync_log_level(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count(argc, 1); + std::string log_level = Value::validated_to_string(ctx, arguments[0]); + std::istringstream in(log_level); // Throws + in.imbue(std::locale::classic()); // Throws + in.unsetf(std::ios_base::skipws); + util::Logger::Level log_level_2 = util::Logger::Level(); + in >> log_level_2; // Throws + if (!in || !in.eof()) + throw std::runtime_error("Bad log level"); + realm::SyncManager::shared().set_log_level(log_level_2); +} + +template +void SyncClass::set_verify_servers_ssl_certificate(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count(argc, 1); + bool verify_servers_ssl_certificate = Value::validated_to_boolean(ctx, arguments[0]); + realm::SyncManager::shared().set_client_should_validate_ssl(verify_servers_ssl_certificate); +} + +#if REALM_HAVE_NODE_SYNC_LOGGER +template +void SyncClass::set_sync_logger(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count(argc, 1); + auto callback = Value::validated_to_function(ctx, arguments[0]); + node::SyncLoggerFactory *factory = new node::SyncLoggerFactory(ctx, this_object, callback); // Throws + realm::SyncManager::shared().set_logger_factory(*factory); +} +#endif + +template +void SyncClass::refresh_access_token(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count(argc, 2); + + static const String token_string = "token"; + static const String file_url_string = "file_url"; + static const String realm_url_string = "resolved_realm_url"; + + ObjectType json_arguments = Value::validated_to_object(ctx, arguments[1]); + std::string token = Object::validated_get_string(ctx, json_arguments, token_string); + std::string file_url = Object::validated_get_string(ctx, json_arguments, file_url_string); + std::string realm_url = Object::validated_get_string(ctx, json_arguments, realm_url_string); + + if (auto session = SyncManager::shared().get_existing_active_session(file_url)) { + session->refresh_access_token(token, realm_url); + return_value.set(true); + } + else { + return_value.set(false); + } +} + +template +void SyncClass::populate_sync_config(ContextType ctx, ObjectType config_object, Realm::Config& config) { + ValueType sync_config_value = Object::get_property(ctx, config_object, "sync"); + if (!Value::is_undefined(ctx, sync_config_value)) { + auto sync_config_object = Value::validated_to_object(ctx, sync_config_value); + + ObjectType user = Object::validated_get_object(ctx, sync_config_object, "user"); + config.sync_config = std::shared_ptr( + new SyncConfig(Object::validated_get_string(ctx, user, "identity"), + Object::validated_get_string(ctx, sync_config_object, "url"), + {}, SyncSessionStopPolicy::AfterChangesUploaded) + ); + config.schema_mode = SchemaMode::Additive; + } +} + +template +void SyncClass::get_is_developer_edition(ContextType ctx, ObjectType object, ReturnValue &return_value) { +#if REALM_DEVELOPER_EDITION + return_value.set(true); +#else + return_value.set(false); +#endif +} + +} // js +} // realm diff --git a/src/js_types.hpp b/src/js_types.hpp index 2b1f1fbc..6c2e78a1 100644 --- a/src/js_types.hpp +++ b/src/js_types.hpp @@ -133,6 +133,11 @@ struct Function { using ValueType = typename T::Value; static ValueType call(ContextType, const FunctionType &, const ObjectType &, size_t, const ValueType[]); + template static ValueType call(ContextType ctx, const FunctionType &function, + const ObjectType &this_object, const ValueType (&arguments)[N]) + { + return call(ctx, function, this_object, N, arguments); + } static ValueType call(ContextType ctx, const FunctionType &function, size_t argument_count, const ValueType arguments[]) { return call(ctx, function, {}, argument_count, arguments); } @@ -247,6 +252,10 @@ class Protected { bool operator!=(const ValueType &) const; bool operator==(const Protected &) const; bool operator!=(const Protected &) const; + + struct Comparator { + bool operator()(const Protected& a, const Protected& b) const; + }; }; template diff --git a/src/jsc/jsc_protected.hpp b/src/jsc/jsc_protected.hpp index 6c20d039..07229f9b 100644 --- a/src/jsc/jsc_protected.hpp +++ b/src/jsc/jsc_protected.hpp @@ -47,6 +47,12 @@ class Protected { operator bool() const { return m_context != nullptr; } + + struct Comparator { + bool operator() (const Protected& a, const Protected& b) const { + return a.m_context == b.m_context; + } + }; }; template<> @@ -75,6 +81,21 @@ class Protected { operator bool() const { return m_value != nullptr; } + + struct Comparator { + bool operator() (const Protected& a, const Protected& b) const { + if (a.m_context != b.m_context) { + return false; + } + return JSValueIsStrictEqual(a.m_context, a.m_value, b.m_value); + } + }; + + Protected& operator=(Protected other) { + std::swap(m_context, other.m_context); + std::swap(m_value, other.m_value); + return *this; + } }; template<> @@ -89,6 +110,11 @@ class Protected : public Protected { JSValueRef value = static_cast(*this); return (JSObjectRef)value; } + + Protected& operator=(Protected other) { + std::swap(*this, other); + return *this; + } }; } // js diff --git a/src/jsc/jsc_types.hpp b/src/jsc/jsc_types.hpp index 9eca89cd..e7b9a868 100644 --- a/src/jsc/jsc_types.hpp +++ b/src/jsc/jsc_types.hpp @@ -34,6 +34,7 @@ struct Types { using Object = JSObjectRef; using String = JSStringRef; using Function = JSObjectRef; + using HandleScope = void *; using ConstructorCallback = JSObjectCallAsConstructorCallback; using FunctionCallback = JSObjectCallAsFunctionCallback; diff --git a/src/node/binding.gyp b/src/node/binding.gyp deleted file mode 100644 index 6e82813a..00000000 --- a/src/node/binding.gyp +++ /dev/null @@ -1,85 +0,0 @@ -{ - "targets": [ - { - "target_name": "realm", - "sources": [ - "node_init.cpp", - "platform.cpp", - "../js_realm.cpp", - "../object-store/src/collection_notifications.cpp", - "../object-store/src/index_set.cpp", - "../object-store/src/list.cpp", - "../object-store/src/object_schema.cpp", - "../object-store/src/object_store.cpp", - "../object-store/src/results.cpp", - "../object-store/src/schema.cpp", - "../object-store/src/shared_realm.cpp", - "../object-store/src/thread_confined.cpp", - "../object-store/src/impl/collection_change_builder.cpp", - "../object-store/src/impl/collection_notifier.cpp", - "../object-store/src/impl/handover.cpp", - "../object-store/src/impl/list_notifier.cpp", - "../object-store/src/impl/realm_coordinator.cpp", - "../object-store/src/impl/results_notifier.cpp", - "../object-store/src/impl/transact_log_handler.cpp", - "../object-store/src/impl/node/weak_realm_notifier.cpp", - "../object-store/src/parser/parser.cpp", - "../object-store/src/parser/query_builder.cpp", - "../object-store/src/util/format.cpp", - "../object-store/src/util/thread_id.cpp" - ], - "include_dirs": [ - "..", - "../object-store/src", - "../object-store/src/impl", - "../object-store/src/impl/apple", - "../object-store/src/parser", - "../object-store/external/pegtl", - "../../core-node/include", - "() const { return Nan::New(m_value); } - operator bool() const { + explicit operator bool() const { return m_value.isEmpty(); } bool operator==(const v8::Local &other) const { @@ -50,6 +50,12 @@ class Protected { bool operator!=(const Protected &other) const { return m_value != other.m_value; } + + struct Comparator { + bool operator()(const Protected& a, const Protected& b) const { + return Nan::New(a.m_value)->StrictEquals(Nan::New(b.m_value)); + } + }; }; } // node diff --git a/src/node/node_sync_logger.cpp b/src/node/node_sync_logger.cpp new file mode 100644 index 00000000..5d66bbc6 --- /dev/null +++ b/src/node/node_sync_logger.cpp @@ -0,0 +1,94 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +#include "node_init.hpp" +#include "node_class.hpp" +#include "js_realm.hpp" +#include "node_types.hpp" +#include "node_sync_logger.hpp" + +namespace realm { +namespace node { + +SyncLoggerQueue::~SyncLoggerQueue() noexcept +{ + m_callback_this_object.Reset(); + m_callback.Reset(); +} + +void SyncLoggerQueue::log_uv_callback() +{ + // This function is always executed by the Node.js event loop + // thread. + Nan::HandleScope scope; + v8::Local this_object = Nan::New(m_callback_this_object); + v8::Local callback = Nan::New(m_callback); + + std::queue popped; + { + std::lock_guard lock(m_mutex); // Throws + popped.swap(m_log_queue); + } + + for (;;) { + if (popped.empty()) + break; + + Nan::TryCatch trycatch; + v8::Local argv[] = { + Nan::New((int)(popped.front().m_level)), + Nan::New(popped.front().m_message).ToLocalChecked() + }; + Nan::MakeCallback(this_object, callback, 2, argv); + if (trycatch.HasCaught()) { + throw node::Exception(m_v8_isolate, trycatch.Exception()); + } + popped.pop(); + } +} + +void SyncLogger::do_log(realm::util::Logger::Level level, std::string message) +{ + std::lock_guard lock(m_mutex); // Throws + m_log_queue.emplace(level, message); + m_log_uv_async.send(); +} + +SyncLoggerFactory::~SyncLoggerFactory() noexcept +{ + m_callback_this_object.Reset(); + m_callback.Reset(); +} + +std::unique_ptr SyncLoggerFactory::make_logger(util::Logger::Level level) +{ + v8::Local this_object = Nan::New(m_callback_this_object); + v8::Local callback = Nan::New(m_callback); + + SyncLogger *logger = new SyncLogger(m_v8_isolate, this_object, callback); // Throws + logger->set_level_threshold(level); + + return std::unique_ptr(logger); +} + +} // namespace node +} // namespace realm diff --git a/src/node/node_sync_logger.hpp b/src/node/node_sync_logger.hpp new file mode 100644 index 00000000..aec87cbd --- /dev/null +++ b/src/node/node_sync_logger.hpp @@ -0,0 +1,89 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#ifndef REALM_NODE_GLOBAL_LOGGER_HPP +#define REALM_NODE_GLOBAL_LOGGER_HPP + +#include +#include +#include + +#include + +#include "node_uv_async.hpp" + +namespace realm { +namespace node { + + struct SyncLoggerMessage { + std::string m_message; + realm::util::Logger::Level m_level; + SyncLoggerMessage(realm::util::Logger::Level level, std::string message): + m_message(message), + m_level(level) { } + }; + + class SyncLoggerQueue { + public: + SyncLoggerQueue(v8::Isolate* v8_isolate, v8::Local callback_this_object, v8::Local callback) : + m_log_uv_async([this] { log_uv_callback(); }), // Throws + m_v8_isolate(v8_isolate), + m_callback_this_object(callback_this_object), + m_callback(callback) { } + ~SyncLoggerQueue() noexcept; + + protected: + void log_uv_callback(); + std::queue m_log_queue; + std::mutex m_mutex; + UvAsync m_log_uv_async; + + private: + v8::Isolate* m_v8_isolate; + Nan::Persistent m_callback_this_object; + Nan::Persistent m_callback; + }; + + class SyncLogger: public realm::util::RootLogger, public SyncLoggerQueue { + public: + SyncLogger(v8::Isolate* v8_isolate, v8::Local callback_this_object, v8::Local callback) : + SyncLoggerQueue(v8_isolate, callback_this_object, callback) { } + + protected: + void do_log(realm::util::Logger::Level, std::string) override final; + }; + + class SyncLoggerFactory : public realm::SyncLoggerFactory { + public: + SyncLoggerFactory(v8::Isolate* v8_isolate, v8::Local callback_this_object, v8::Local callback) : + m_v8_isolate(v8_isolate), + m_callback_this_object(callback_this_object), + m_callback(callback) { } + ~SyncLoggerFactory() noexcept; + + virtual std::unique_ptr make_logger(util::Logger::Level level); + private: + v8::Isolate* m_v8_isolate; + Nan::Persistent m_callback_this_object; + Nan::Persistent m_callback; + }; + +} // namespace node +} // namespace realm + +#endif // REALM_NODE_GLOBAL_LOGGER_HPP diff --git a/src/node/node_types.hpp b/src/node/node_types.hpp index 4220c173..f38b8c16 100644 --- a/src/node/node_types.hpp +++ b/src/node/node_types.hpp @@ -41,6 +41,7 @@ struct Types { using Object = v8::Local; using String = v8::Local; using Function = v8::Local; + using HandleScope = Nan::HandleScope; using ConstructorCallback = Nan::FunctionCallback; using FunctionCallback = Nan::FunctionCallback; diff --git a/src/node/node_uv_async.hpp b/src/node/node_uv_async.hpp new file mode 100644 index 00000000..05c14949 --- /dev/null +++ b/src/node/node_uv_async.hpp @@ -0,0 +1,104 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#ifndef REALM_NODE_UV_ASYNC_HPP +#define REALM_NODE_UV_ASYNC_HPP + +#include +#include +#include +#include + +#include + +namespace realm { +namespace node { + +// Must be created and destroyed by the thread that is associated with the +// specified libuv event loop. +class UvAsync { +public: + UvAsync(std::function func, uv_loop_t* = uv_default_loop()); + ~UvAsync() noexcept; + + /// Schedule the associated callback function to be executed by the + /// associated libuv event loop. May be called by any thread. + void send(); + +private: + struct Rep; + + Rep* m_rep; + + static void exec(uv_async_t* handle) noexcept; + static void close(uv_handle_t* handle) noexcept; +}; + + + + +// implementation + +struct UvAsync::Rep { + uv_async_t handle; + std::function func; +}; + +inline UvAsync::UvAsync(std::function func, uv_loop_t* loop) +{ + std::unique_ptr rep(new Rep); + rep->handle.data = &*rep; + rep->func = std::move(func); + int ret = uv_async_init(loop, &rep->handle, &UvAsync::exec); + if (ret < 0) + throw std::runtime_error("uv_async_init() failed"); + m_rep = rep.release(); +} + +inline UvAsync::~UvAsync() noexcept +{ + m_rep->func = std::function(); + uv_close(reinterpret_cast(&m_rep->handle), &UvAsync::close); +} + +inline void UvAsync::send() +{ + int ret = uv_async_send(&m_rep->handle); + if (ret < 0) + throw std::runtime_error("uv_async_send() failed"); +} + +inline void UvAsync::exec(uv_async_t* handle) noexcept +{ + Rep* rep = static_cast(handle->data); + if (rep->func) + rep->func(); // Throws + + // FIXME: How to deal with C++ exceptions here? +} + +inline void UvAsync::close(uv_handle_t* handle) noexcept +{ + Rep* rep = static_cast(handle->data); + delete rep; +} + +} // namespace node +} // namespace realm + +#endif // REALM_NODE_UV_ASYNC_HPP diff --git a/src/node/node_value.hpp b/src/node/node_value.hpp index 0a25ad7f..6909eb80 100644 --- a/src/node/node_value.hpp +++ b/src/node/node_value.hpp @@ -129,7 +129,7 @@ inline bool node::Value::to_boolean(v8::Isolate* isolate, const v8::Local inline double node::Value::to_number(v8::Isolate* isolate, const v8::Local &value) { double number = Nan::To(value).FromMaybe(NAN); - if (isnan(number)) { + if (std::isnan(number)) { throw std::invalid_argument("Value not convertible to a number."); } return number; diff --git a/test.js b/test.js new file mode 100644 index 00000000..d8d33135 --- /dev/null +++ b/test.js @@ -0,0 +1,85 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +'use strict'; + +const Realm = require('.'); +const prompt = require('prompt'); +const mkdirp = require('mkdirp'); +const wildcard = require('wildcard'); + +// User.loginWithProvider('http://127.0.0.1:8080/', 'debug', 'abcd', function(error, user) { +// console.log(user); +// }); + +Realm.Sync.User.create('http://127.0.0.1:9080/', 'ari', 'aaa', function(error, user) { + +var notifier_dir = './notifier'; +mkdirp.sync(notifier_dir); + +var access_token = 'ewoJImlkZW50aXR5IjogImFkbWluIiwKCSJhY2Nlc3MiOiBbInVwbG9hZCIsICJkb3dubG9hZCIsICJtYW5hZ2UiXQp9Cg==:DlFksxA+cJyEOc9bu6JwBUfDi4fJCagjAcIPPsoisjqfmOzSrk5Omuw0IkxCRU534p2+CAAj5IOH47DfObPtAA8q2DHguYDOKWYxyktS/6doPCqDHYN7k9EgUHdPTkESNkuPZbaVfXZTGzocB8m7+MaEXJde7FGPbh1sBz/+sPldnlAhnOqO5QbWzIEyoGHiOSg3V7UCh2H8kalr3tef7fkE2X65OBMgcarPvM5M6sPijOx2N5zrVrjL2wvguP9zS+g2ybFPUqV3DGv3S8cnGA+wVId/jCfGc2ujNhecunJdENH+/pL+0BTYHCFEWkY1WP1NUyti60FwRaXAtcYxeA=='; +var admin_user = new Realm.Sync.User.adminUser('http://127.0.0.1:9080/', access_token); + +Realm.Sync.setLogLevel('error'); +Realm.Sync.setGlobalListener(notifier_dir, 'realm://127.0.0.1:9080', admin_user, + (name) => { + console.log('filter: ' + name); + return true; + }, + (name, realm, changes) => { + console.log('change: ' + name); + console.log(changes); + } +); +console.log('global notifier listening...'); + +Realm.Sync.User.login('http://127.0.0.1:9080/', 'ari', 'aaa', function(error, user) { + console.log(user); + + var realm = new Realm({ + sync: { + user: user, + url: 'realm://127.0.0.1:9080/~/demo/realm1' + }, + schema: [{ + name: 'IntObject', + properties: { + int: 'int' + } + }] + }); + + function create(err, result) { + if (err) { + exit(); + } + if (!err) { + realm.write(() => { + realm.create('IntObject', {int: parseInt(result.int)}); + }); + console.log(realm.objects('IntObject')); + } + + prompt.get(['int'], create); + } + prompt.start(); + prompt.get(['int'], create); +}); + +}); + diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 00000000..56b36ece --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,2 @@ +/sync-bundle/ +/junitresults-*.xml \ No newline at end of file diff --git a/tests/index.js b/tests/index.js index e22b797a..09fd9a9d 100644 --- a/tests/index.js +++ b/tests/index.js @@ -23,14 +23,13 @@ const fs = require('fs'); const path = require('path'); -const mockery = require('mockery'); function runTests() { const Realm = require('realm'); const RealmTests = require('./js'); RealmTests.registerTests({ - WorkerTests: require('./js/worker-tests'), + AsyncTests: require('./js/async-tests'), }); const testNames = RealmTests.getTestNames(); @@ -79,10 +78,6 @@ function runTests() { } if (require.main == module) { - mockery.enable(); - mockery.warnOnUnregistered(false); - mockery.registerMock('realm', require('..')); - runTests().then( (passed) => { if (!passed) { diff --git a/tests/ios/RJSModuleLoader.h b/tests/ios/RJSModuleLoader.h index 91ab5bac..a9c8f77c 100644 --- a/tests/ios/RJSModuleLoader.h +++ b/tests/ios/RJSModuleLoader.h @@ -27,5 +27,6 @@ - (JSValue *)loadModuleFromURL:(NSURL *)url error:(NSError **)error; - (JSValue *)loadJSONFromURL:(NSURL *)url error:(NSError **)error; +- (JSValue *)loadGlobalModule:(NSString *)name relativeToURL:(NSURL *)url error:(NSError **)error; @end diff --git a/tests/ios/RJSModuleLoader.m b/tests/ios/RJSModuleLoader.m index 3c0613a9..39cbb066 100644 --- a/tests/ios/RJSModuleLoader.m +++ b/tests/ios/RJSModuleLoader.m @@ -195,6 +195,18 @@ static NSString * const RJSModuleLoaderErrorDomain = @"RJSModuleLoaderErrorDomai BOOL isDirectory; if ([fileManager fileExistsAtPath:moduleURL.path isDirectory:&isDirectory] && isDirectory) { + NSURL *packageURL = [moduleURL URLByAppendingPathComponent:@"package.json"]; + NSDictionary *package; + + if ([fileManager fileExistsAtPath:packageURL.path]) { + NSError *error; + NSData *data = [NSData dataWithContentsOfURL:packageURL options:0 error:&error]; + + package = data ? [NSJSONSerialization JSONObjectWithData:data options:0 error:&error] : nil; + NSAssert(package, @"%@", error); + } + + moduleURL = [moduleURL URLByAppendingPathComponent:package[@"main"] ?: @"index.js"]; return [self loadModuleFromURL:moduleURL error:error]; } diff --git a/tests/ios/RealmJSCoreTests.m b/tests/ios/RealmJSCoreTests.m index d3bf62c9..5fd0cf91 100644 --- a/tests/ios/RealmJSCoreTests.m +++ b/tests/ios/RealmJSCoreTests.m @@ -31,12 +31,23 @@ + (XCTestSuite *)defaultTestSuite { XCTestSuite *suite = [super defaultTestSuite]; - JSContext *context = [[JSContext alloc] init]; + + // We need a JS context from a UIWebView so it has setTimeout, Promise, etc. + UIWebView *webView = [[UIWebView alloc] init]; + JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; RJSModuleLoader *moduleLoader = [[RJSModuleLoader alloc] initWithContext:context]; NSURL *realmURL = [[NSBundle bundleForClass:self] URLForResource:@"index" withExtension:@"js" subdirectory:@"lib"]; NSURL *scriptURL = [[NSBundle bundleForClass:self] URLForResource:@"index" withExtension:@"js" subdirectory:@"js"]; NSError *error; + // The ES6 global Promise constructor was added in iOS 8. + if (![context[@"Promise"] isObject]) { + JSValue *promiseModule = [moduleLoader loadGlobalModule:@"es6-promise" relativeToURL:scriptURL error:&error]; + NSAssert(promiseModule, @"%@", error); + + context[@"Promise"] = promiseModule[@"Promise"]; + } + // Create Realm constructor in the JS context. RJSInitializeInContext(context.JSGlobalContextRef); @@ -73,25 +84,46 @@ JSContext *context = testObject.context; context.exception = nil; - [testObject invokeMethod:@"runTest" withArguments:@[NSStringFromClass(self.class), method]]; + JSValue *promise = [testObject invokeMethod:@"runTest" withArguments:@[NSStringFromClass(self.class), method]]; - JSValue *exception = context.exception; - if (exception) { - JSValue *message = [exception hasProperty:@"message"] ? exception[@"message"] : exception; - NSString *source = [exception hasProperty:@"sourceURL"] ? [exception[@"sourceURL"] toString] : nil; - NSUInteger line = [exception hasProperty:@"line"] ? [exception[@"line"] toUInt32] - 1 : 0; - NSURL *sourceURL = nil; + if (context.exception) { + [self recordException:context.exception]; + return; + } - if (source) { - NSString *path = [NSString pathWithComponents:@[[@(__FILE__) stringByDeletingLastPathComponent], @"..", @"js", source.lastPathComponent]]; - sourceURL = [NSURL URLWithString:path]; - } + if ([promise isObject]) { + XCTestExpectation *expectation = [self expectationWithDescription:@"Promise resolved or rejected"]; - [self recordFailureWithDescription:message.description - inFile:sourceURL ? sourceURL.absoluteString : @(__FILE__) - atLine:sourceURL ? line : __LINE__ - expected:YES]; + JSValue *onFulfilled = [JSValue valueWithObject:^() { + [expectation fulfill]; + } inContext:context]; + + JSValue *onRejected = [JSValue valueWithObject:^(JSValue *error) { + [self recordException:error]; + [expectation fulfill]; + } inContext:context]; + + [promise invokeMethod:@"then" withArguments:@[onFulfilled, onRejected]]; + + [self waitForExpectationsWithTimeout:5.0 handler:NULL]; } } +- (void)recordException:(JSValue *)exception { + JSValue *message = [exception hasProperty:@"message"] ? exception[@"message"] : exception; + NSString *source = [exception hasProperty:@"sourceURL"] ? [exception[@"sourceURL"] toString] : nil; + NSUInteger line = [exception hasProperty:@"line"] ? [exception[@"line"] toUInt32] - 1 : 0; + NSURL *sourceURL = nil; + + if (source) { + NSString *path = [NSString pathWithComponents:@[[@(__FILE__) stringByDeletingLastPathComponent], @"..", @"js", source.lastPathComponent]]; + sourceURL = [NSURL URLWithString:path]; + } + + [self recordFailureWithDescription:message.description + inFile:sourceURL ? sourceURL.absoluteString : @(__FILE__) + atLine:sourceURL ? line : __LINE__ + expected:YES]; +} + @end diff --git a/tests/js/async-tests.js b/tests/js/async-tests.js new file mode 100644 index 00000000..a9db9c61 --- /dev/null +++ b/tests/js/async-tests.js @@ -0,0 +1,360 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +/* eslint-env es6, node */ + +'use strict'; + +const Realm = require('realm'); +const TestCase = require('./asserts'); +const schemas = require('./schemas'); +const Worker = require('./worker'); + +function createNotificationTest(config, getObservable, addListener, removeListener, messages, expectedCount) { + return new Promise((resolve, reject) => { + let realm = new Realm(config); + let observable = getObservable(realm); + let worker = new Worker(__dirname + '/worker-tests-script.js'); + + // Test will fail if it does not receive a change event within a second. + let timer = setTimeout(() => { + reject(new Error('Timed out waiting for change notification')); + }, 5000); + + let cleanup = () => { + clearTimeout(timer); + worker.terminate(); + }; + + var count = 0; + var listener = addListener(observable, () => count++, resolve, reject, cleanup); + + messages.push(['echo', 'resolve']); + var messageIndex = 0; + + worker.onmessage = (message) => { + if (message.error) { + reject(message.error); + cleanup(); + } + else if (message.result == 'resolve') { + if (count != expectedCount) { + reject('Notification count ' + count + ' not equal to expected count ' + expectedCount); + } + else { + resolve(); + } + cleanup(); + } + else { + if (message.result == 'removeListener') { + removeListener(observable, listener); + } + worker.postMessage(messages[messageIndex++]); + } + }; + + worker.postMessage(messages[messageIndex++]); + }); +}; + +function createCollectionChangeTest(config, createCollection, messages, expected, removeAll) { + return createNotificationTest( + config, + createCollection, + (collection, increment, resolve, reject, cleanup) => { + var listener = (object, changes) => { + try { + var notificationCount = increment(); + TestCase.assertArraysEqual(changes.insertions, expected[notificationCount][0]); + TestCase.assertArraysEqual(changes.deletions, expected[notificationCount][1]); + TestCase.assertArraysEqual(changes.modifications, expected[notificationCount][2]); + } catch (e) { + reject(e); + cleanup(); + } + }; + collection.addListener(listener); + return listener; + }, + removeAll ? (observable) => observable.removeAllListeners() : + (observable, listener) => observable.removeListener(listener), + messages, + expected.length + ); +}; + +const ListObject = { + name: 'ListObject', + properties: { + list: {type: 'list', objectType: 'TestObject'}, + } +}; + +const PrimaryListObject = { + name: 'PrimaryListObject', + properties: { + list: {type: 'list', objectType: 'IntPrimaryObject'}, + } +}; + +module.exports = { + testChangeNotifications() { + var config = { schema: [schemas.TestObject] }; + return createNotificationTest( + config, + (realm) => realm, + (realm, increment, resolve, reject, cleanup) => realm.addListener('change', () => { + try { + var objects = realm.objects('TestObject'); + TestCase.assertEqual(objects.length, 1); + TestCase.assertEqual(objects[0].doubleCol, 42); + increment(); + } catch (e) { + reject(e); + cleanup(); + } + }), + undefined, + [[config, 'create', 'TestObject', [{doubleCol: 42}]]], + 1 + ); + }, + + testResultsAddNotifications() { + var config = { schema: [schemas.TestObject] }; + return createCollectionChangeTest( + config, + (realm) => realm.objects('TestObject'), + [ + [config, 'create', 'TestObject', [{ doubleCol: 1 }]], + [config, 'create', 'TestObject', [{ doubleCol: 2 }, { doubleCol: 3 }]] + ], + [ + [[], [], []], + [[0], [], []], + [[1, 2], [], []], + ] + ); + }, + + testResultsRemoveNotifications() { + var config = { schema: [schemas.TestObject] }; + return createCollectionChangeTest( + config, + (realm) => realm.objects('TestObject'), + [ + [config, 'create', 'TestObject', [{ doubleCol: 1 }]], + ['echo', 'removeListener'], + [config, 'create', 'TestObject', [{ doubleCol: 2 }, { doubleCol: 3 }]] + ], + [ + [[], [], []], + [[0], [], []], + ] + ); + }, + + testResultsRemoveAllNotifications() { + var config = { schema: [schemas.TestObject] }; + return createCollectionChangeTest( + config, + (realm) => realm.objects('TestObject'), + [ + [config, 'create', 'TestObject', [{ doubleCol: 1 }]], + ['echo', 'removeListener'], + [config, 'create', 'TestObject', [{ doubleCol: 2 }, { doubleCol: 3 }]] + ], + [ + [[], [], []], + [[0], [], []], + ], + true + ); + }, + + testResultsDeleteNotifications() { + var config = { schema: [schemas.TestObject] }; + return createCollectionChangeTest( + config, + function(realm) { + return realm.objects('TestObject'); + }, + [ + [config, 'create', 'TestObject', [[0], [1], [2], [3], [4]]], + [config, 'delete', 'TestObject', [4]], + [config, 'delete', 'TestObject', [0, 2]] + ], + [ + [[], [], []], + [[0, 1, 2, 3, 4], [], []], + [[], [4], []], + [[0], [0, 2, 3], []] + ] + ); + }, + + testResultsUpdateNotifications() { + var config = { schema: [schemas.IntPrimary] }; + return createCollectionChangeTest( + config, + (realm) => realm.objects('IntPrimaryObject'), + [ + [config, 'create', 'IntPrimaryObject', [[0, '0'], [1, '1'], [2, '2']]], + [config, 'update', 'IntPrimaryObject', [[0, '00'], [2, '22']]] + ], + [ + [[], [], []], + [[0, 1, 2], [], []], + [[], [], [0, 2]] + ] + ); + }, + + testListAddNotifications() { + var config = { schema: [schemas.TestObject, ListObject] }; + return createCollectionChangeTest( + config, + function(realm) { + let listObject; + realm.write(() => { + listObject = realm.create('ListObject', {list: []}) + }); + return listObject.list; + }, + [ + [config, 'list_method', 'ListObject', 'list', 'push', {doubleCol: 0}, {doubleCol: 1}] + ], + [ + [[], [], []], + [[0, 1], [], []] + ] + ); + }, + + testListRemoveNotifications() { + var config = { schema: [schemas.TestObject, ListObject] }; + return createCollectionChangeTest( + config, + function(realm) { + let listObject; + realm.write(() => { + listObject = realm.create('ListObject', {list: []}) + }); + return listObject.list; + }, + [ + [config, 'list_method', 'ListObject', 'list', 'push', {doubleCol: 0}, {doubleCol: 1}], + ['echo', 'removeListener'], + [config, 'list_method', 'ListObject', 'list', 'push', {doubleCol: 0}, {doubleCol: 1}], + ], + [ + [[], [], []], + [[0, 1], [], []] + ] + ); + }, + + testListRemoveAllNotifications() { + var config = { schema: [schemas.TestObject, ListObject] }; + return createCollectionChangeTest( + config, + function(realm) { + let listObject; + realm.write(() => { + listObject = realm.create('ListObject', {list: []}) + }); + return listObject.list; + }, + [ + [config, 'list_method', 'ListObject', 'list', 'push', {doubleCol: 0}, {doubleCol: 1}], + ['echo', 'removeListener'], + [config, 'list_method', 'ListObject', 'list', 'push', {doubleCol: 0}, {doubleCol: 1}], + ], + [ + [[], [], []], + [[0, 1], [], []] + ], + true + ); + }, + + testListDeleteNotifications() { + var config = { schema: [schemas.TestObject, ListObject] }; + return createCollectionChangeTest( + config, + function(realm) { + let listObject; + realm.write(() => { + listObject = realm.create('ListObject', {list: [[0], [1], [2]]}) + }); + return listObject.list; + }, + [ + [config, 'list_method', 'ListObject', 'list', 'splice', 1, 2] + ], + [ + [[], [], []], + [[], [1, 2], []] + ] + ); + }, + + testListSpliceNotifications() { + var config = { schema: [schemas.TestObject, ListObject] }; + return createCollectionChangeTest( + config, + function(realm) { + let listObject; + realm.write(() => { + listObject = realm.create('ListObject', {list: [[0], [1], [2]]}) + }); + return listObject.list; + }, + [ + [config, 'list_method', 'ListObject', 'list', 'splice', 1, 1, [2]] + ], + [ + [[], [], []], + [[1], [1], []] + ] + ); + }, + + testListUpdateNotifications() { + var config = { schema: [schemas.IntPrimary, PrimaryListObject] }; + return createCollectionChangeTest( + config, + function(realm) { + let listObject; + realm.write(() => { + listObject = realm.create('PrimaryListObject', {list: [[0, '0'], [1, '1']]}) + }); + return listObject.list; + }, + [ + [config, 'update', 'IntPrimaryObject', [[1, '11']]] + ], + [ + [[], [], []], + [[], [], [1]] + ] + ); + }, +}; + diff --git a/tests/js/package.json b/tests/js/package.json index 07bbe26a..c7afa5d5 100644 --- a/tests/js/package.json +++ b/tests/js/package.json @@ -1,5 +1,8 @@ { "name": "realm-tests", "version": "0.0.1", - "private": true + "private": true, + "dependencies": { + "es6-promise": "^3.2.1" + } } diff --git a/tests/js/realm-tests.js b/tests/js/realm-tests.js index a9fca21e..406b88b7 100644 --- a/tests/js/realm-tests.js +++ b/tests/js/realm-tests.js @@ -33,7 +33,7 @@ module.exports = { testRealmConstructorPath: function() { TestCase.assertThrows(function() { - new Realm('/invalidpath'); + new Realm(''); }, 'Realm cannot be created with an invalid path'); TestCase.assertThrows(function() { new Realm('test1.realm', 'invalidArgument'); diff --git a/tests/js/worker-tests-script.js b/tests/js/worker-tests-script.js index ec16d13b..9fa29a05 100644 --- a/tests/js/worker-tests-script.js +++ b/tests/js/worker-tests-script.js @@ -23,30 +23,39 @@ const Realm = require('../..'); -const handlers = { - create(options) { - let realm = new Realm(options.config); - - realm.write(() => { - realm.create(options.type, options.properties); - }); - } -}; - process.on('message', (message) => { process.send(handleMessage(message)); }); function handleMessage(message) { let error, result; + if (message[0] == 'echo') { + return {result: message[1]} + } try { - let handler = handlers[message.action]; - if (handler) { - result = handler(message); - } else { - throw new Error('Unknown worker action: ' + message.action); - } + let realm = new Realm(message[0]); + realm.write(() => { + if (message[1] == 'create') { + result = message[3].map((value) => realm.create(message[2], value)); + } + else if (message[1] == 'delete') { + let objects = realm.objects(message[2]); + objects = message[3].map((index) => objects[index]); + realm.delete(objects); + } + else if (message[1] == 'update') { + result = message[3].map((value) => realm.create(message[2], value, true)); + } + else if (message[1] == 'list_method') { + var listObject = realm.objects(message[2])[0]; + var list = listObject[message[3]]; + result = list[message[4]].apply(list, message.slice(5)); + } + else { + throw new Error('Unknown realm method: ' + message[1]); + } + }); } catch (e) { console.warn(e); error = e.message; diff --git a/tests/js/worker-tests.js b/tests/js/worker-tests.js deleted file mode 100644 index d3bbba4f..00000000 --- a/tests/js/worker-tests.js +++ /dev/null @@ -1,76 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -/* eslint-env es6, node */ - -'use strict'; - -const Realm = require('realm'); -const TestCase = require('./asserts'); -const schemas = require('./schemas'); -const Worker = require('./worker'); - -module.exports = { - testChangeNotifications() { - return new Promise((resolve, reject) => { - let config = {schema: [schemas.TestObject]}; - let realm = new Realm(config); - let objects = realm.objects('TestObject'); - let worker = new Worker(__dirname + '/worker-tests-script.js'); - - // Test will fail if it does not receive a change event within a second. - let timer = setTimeout(() => { - reject(new Error('Timed out waiting for change notification')); - }, 1000); - - let cleanup = () => { - clearTimeout(timer); - worker.terminate(); - }; - - // Test will pass if it receives a change event and the Realm changed. - realm.addListener('change', () => { - try { - TestCase.assertEqual(objects.length, 1); - TestCase.assertEqual(objects[0].doubleCol, 42); - resolve(); - } catch (e) { - reject(e); - } finally { - cleanup(); - } - }); - - worker.onmessage = (message) => { - if (message.error) { - cleanup(); - reject(message.error); - } - }; - - worker.postMessage({ - action: 'create', - config: config, - type: 'TestObject', - properties: { - doubleCol: 42, - } - }); - }); - } -}; diff --git a/tests/js/worker.js b/tests/js/worker.js index 86b1b6f0..0cf290b3 100644 --- a/tests/js/worker.js +++ b/tests/js/worker.js @@ -31,7 +31,9 @@ class Worker { }); } postMessage(message) { - this._process.send(message); + if (this._process) { + this._process.send(message); + } } terminate() { if (this._process) { diff --git a/tests/package.json b/tests/package.json new file mode 100644 index 00000000..682f5733 --- /dev/null +++ b/tests/package.json @@ -0,0 +1,16 @@ +{ + "name": "realm-tests-jasmine", + "version": "0.0.1", + "private": true, + "dependencies": { + "es6-promise": "^3.2.1", + "jasmine": "^2.5.1", + "jasmine-console-reporter": "^1.2.7", + "jasmine-reporters": "^2.2.0" + }, + "scripts": { + "test": "jasmine spec/unit_tests.js", + "test-sync": "jasmine spec/sync_integration_tests.js", + "postinstall": "rm -f node_modules/realm && ln -s ../.. node_modules/realm" + } +} diff --git a/tests/react-test-app/index.ios.js b/tests/react-test-app/index.ios.js index d1aaaa7b..8f3b7c67 100644 --- a/tests/react-test-app/index.ios.js +++ b/tests/react-test-app/index.ios.js @@ -28,6 +28,36 @@ import { import React from 'react'; import { runTests } from './tests'; +const Realm = require('realm'); + +Realm.Sync.setLogLevel('error'); +var realm; +Realm.Sync.User.login('http://127.0.0.1:9080/', 'ari', 'aaa', function(error, user) { + console.log(user); + + realm = new Realm({ + sync: { + user: user, + url: 'realm://127.0.0.1:9080/~/demo/realm1' + }, + schema: [{ + name: 'IntObject', + properties: { + int: 'int' + } + }] + }); + + realm.addListener('change', () => { + console.log(realm.objects('IntObject')); + }); + + realm.write(() => { + realm.create('IntObject', {int: realm.objects('IntObject').length}); + }); +}); + + class ReactTests extends React.Component { render() { return ( diff --git a/tests/react-test-app/ios/ReactTests.xcodeproj/xcshareddata/xcschemes/ReactTestApp.xcscheme b/tests/react-test-app/ios/ReactTests.xcodeproj/xcshareddata/xcschemes/ReactTestApp.xcscheme index dfdb9ee4..c3a683d9 100644 --- a/tests/react-test-app/ios/ReactTests.xcodeproj/xcshareddata/xcschemes/ReactTestApp.xcscheme +++ b/tests/react-test-app/ios/ReactTests.xcodeproj/xcshareddata/xcschemes/ReactTestApp.xcscheme @@ -91,6 +91,13 @@ ReferencedContainer = "container:ReactTests.xcodeproj"> + + + + diff --git a/tests/react-test-app/package.json b/tests/react-test-app/package.json index 1799ba2c..4319ee6d 100644 --- a/tests/react-test-app/package.json +++ b/tests/react-test-app/package.json @@ -6,11 +6,11 @@ "start": "react-native start" }, "dependencies": { - "react": "~15.2.0", - "react-native": "^0.31.0", + "react": "~15.3.1", + "react-native": "^0.34.0", "react-native-fs": "^1.1.0", - "xmlbuilder": "^4.2.1", "realm": "file:../..", - "realm-tests": "file:../js" + "realm-tests": "file:../js", + "xmlbuilder": "^4.2.1" } } diff --git a/tests/spec/helpers/reporters.js b/tests/spec/helpers/reporters.js new file mode 100644 index 00000000..5e56821a --- /dev/null +++ b/tests/spec/helpers/reporters.js @@ -0,0 +1,9 @@ +var jasmineReporters = require('jasmine-reporters'); +var junitReporter = new jasmineReporters.JUnitXmlReporter({ + savePath: '.', + consolidateAll: false +}); +jasmine.getEnv().addReporter(junitReporter); + +var JasmineConsoleReporter = require('jasmine-console-reporter'); +jasmine.getEnv().addReporter(new JasmineConsoleReporter()); \ No newline at end of file diff --git a/tests/spec/support/jasmine.json b/tests/spec/support/jasmine.json new file mode 100644 index 00000000..3ea31669 --- /dev/null +++ b/tests/spec/support/jasmine.json @@ -0,0 +1,11 @@ +{ + "spec_dir": "spec", + "spec_files": [ + "**/*[sS]pec.js" + ], + "helpers": [ + "helpers/**/*.js" + ], + "stopSpecOnExpectationFailure": false, + "random": false +} diff --git a/tests/spec/sync_integration_tests.js b/tests/spec/sync_integration_tests.js new file mode 100644 index 00000000..c4ac38fd --- /dev/null +++ b/tests/spec/sync_integration_tests.js @@ -0,0 +1,99 @@ +"use strict"; + +const spawn = require("child_process").spawn; +const readline = require("readline"); +const fs = require("fs"); +const Realm = require("realm"); + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000; + +beforeEach(function(done) { + this.objectServer = spawn("sync-bundle/start-object-server.command"); + this.objectServer.once("close", (code) => { + if (typeof code === "number" && code != 0) { + console.error(`Object Server exited with code ${code}`); + process.exit(-1); + } + }); + + this.rl = readline.createInterface({ input: this.objectServer.stdout }); + this.rl.on("line", (line) => { + var match; + if ((match = line.match(/Connection\[1\]: Session\[1\]: Received: BIND\(server_path='\/(.+)',/))) { + var adminUser = Realm.Sync.User.adminUser('http://127.0.0.1:9080/', + fs.readFileSync("sync-bundle/admin_token.base64", "utf-8")); + this.adminRealmPath = match[1]; + this.adminRealm = new Realm({ + path: "__admin.realm", + sync: { + user: adminUser, + url: `realm://127.0.0.1:9080/${this.adminRealmPath}` + }, + schema: [ + { + name: "RealmFile", + properties: { + id: 'string', + path: 'string' + } + } + ] + }); + + done(); + } + }); +}); + +afterEach(function(done) { + this.rl.close(); + this.objectServer.kill('SIGKILL'); + this.adminRealm.close(); + + let reset = spawn("sync-bundle/reset-server-realms.command"); + reset.once("close", done); + reset.stdin.write("yes\n"); + + Realm.clearTestState(); +}); + +describe("Sync", function() { + it("should work", function(done) { + Realm.Sync.User.create('http://127.0.0.1:9080/', 'foo', 'bar', function(error) { + if (error) { + fail(error); + return; + } + + Realm.Sync.User.login('http://127.0.0.1:9080/', 'foo', 'bar', function(error, user) { + if (error) { + fail(error); + return; + } + + var realm = new Realm({ + syncConfig: { + identity: user.identity, + url: 'realm://127.0.0.1:9080/~/demo/realm1' + }, + schema: [ + { + name: 'IntObject', + properties: { + int: 'int' + } + } + ] + }); + }); + }); + + var realms = this.adminRealm.objects("RealmFile"); + realms.addListener((sender, changeset) => { + if (changeset.insertions.length === 1) { + expect(realms[changeset.insertions[0]].path).toMatch(/demo\/realm1$/); + done(); + } + }); + }); +}); \ No newline at end of file diff --git a/tests/spec/unit_tests.js b/tests/spec/unit_tests.js new file mode 100644 index 00000000..91ecec09 --- /dev/null +++ b/tests/spec/unit_tests.js @@ -0,0 +1,79 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +/* eslint-env es6, node */ +/* eslint-disable no-console */ + +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +const Realm = require('../..'); +const RealmTests = require('../js'); + +// Create this method with appropriate implementation for Node testing. +Realm.copyBundledRealmFiles = function() { + let sourceDir = path.join(__dirname, '../data'); + let destinationDir = path.dirname(Realm.defaultPath); + + for (let filename of fs.readdirSync(sourceDir)) { + let src = path.join(sourceDir, filename); + let dest = path.join(destinationDir, filename); + + // If the destination file already exists, then don't overwrite it. + try { + fs.accessSync(dest); + continue; + } catch (e) {} + + fs.writeFileSync(dest, fs.readFileSync(src)); + } +}; + +const tests = RealmTests.getTestNames(); +for (const suiteName in tests) { + describe(suiteName, () => { + beforeEach(() => RealmTests.runTest(suiteName, 'beforeEach')); + + for (const testName of tests[suiteName]) { + it(testName, () => { + try { + RealmTests.runTest(suiteName, testName) + } + catch (e) { + fail(e); + } + + }); + } + + afterEach(() => RealmTests.runTest(suiteName, 'afterEach')); + }); +} + +const asyncTests = require('../js/async-tests'); +describe('AsyncTests', () => { + beforeEach(() => Realm.clearTestState()); + + for (const testName in asyncTests) { + it(testName, (done) => asyncTests[testName]().catch((e) => fail(e)).then(done)); + } + + afterEach(() => Realm.clearTestState()); +}); diff --git a/vendor/.gitignore b/vendor/.gitignore new file mode 100644 index 00000000..5b026cbf --- /dev/null +++ b/vendor/.gitignore @@ -0,0 +1 @@ +/realm/