From 966230c7a758824ad3d4f2f34df1d8c0b3ddb102 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Thu, 22 Feb 2018 20:38:18 +0100 Subject: [PATCH 01/39] Validate that a given type appears at most once in the schema (#1670) --- CHANGELOG.md | 15 +++++++++++++++ src/object-store | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 700d4b44..22f3d8be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +X.Y.Z Release notes +============================================================= +### Breaking changes +* None. + +### Enhancements +* None. + +### Bug fixes +* Validate that a given type appears at most once in the schema. + +### Internal +* None. + + 2.2.10 Release notes (2018-2-20) ============================================================= ### Breaking changes diff --git a/src/object-store b/src/object-store index 9aab0ffd..d5fe9a62 160000 --- a/src/object-store +++ b/src/object-store @@ -1 +1 @@ -Subproject commit 9aab0ffd5bc7bfc438ec28375ba581cf732f57ee +Subproject commit d5fe9a626c0adc0345d128201e0df8bb4004725d From 0adadbb19185d670da1f5306f10e2c7c092843c6 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Thu, 22 Feb 2018 20:41:07 +0100 Subject: [PATCH 02/39] [2.2.11] Bump version --- CHANGELOG.md | 2 +- dependencies.list | 2 +- package.json | 2 +- src/RealmJS.xcodeproj/project.pbxproj | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22f3d8be..9ae986e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -X.Y.Z Release notes +2.2.11 Release notes (2018-2-22) ============================================================= ### Breaking changes * None. diff --git a/dependencies.list b/dependencies.list index 96b64f81..f99f8f60 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js -VERSION=2.2.10 +VERSION=2.2.11 REALM_CORE_VERSION=5.1.2 REALM_SYNC_VERSION=2.2.12 REALM_OBJECT_SERVER_VERSION=2.5.1 diff --git a/package.json b/package.json index 58fcf169..38f9443a 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": "2.2.10", + "version": "2.2.11", "license": "Apache-2.0", "homepage": "https://realm.io", "keywords": [ diff --git a/src/RealmJS.xcodeproj/project.pbxproj b/src/RealmJS.xcodeproj/project.pbxproj index 36ad277e..4204d9dc 100644 --- a/src/RealmJS.xcodeproj/project.pbxproj +++ b/src/RealmJS.xcodeproj/project.pbxproj @@ -961,7 +961,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2.2.10; + CURRENT_PROJECT_VERSION = 2.2.11; CXX = "$(SRCROOT)/../scripts/ccache-clang++.sh"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -1025,7 +1025,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2.2.10; + CURRENT_PROJECT_VERSION = 2.2.11; CXX = "$(SRCROOT)/../scripts/ccache-clang++.sh"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; From 3a82a46fb94726cf7fab4edf7ff3e0493049fce9 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Thu, 22 Feb 2018 22:59:25 +0100 Subject: [PATCH 03/39] [2.2.11] Bump version - 2nd attempt --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 38f9443a..15e44ed3 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "decompress": "^4.2.0", "fs-extra": "^4.0.2", "ini": "^1.3.4", - "nan": "^2.3.3", + "nan": "2.8.0", "node-fetch": "^1.6.3", "node-pre-gyp": "^0.6.36", "progress": "^2.0.0", From abe5287333378ce1ce051cecb78068d850509d7b Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Fri, 23 Feb 2018 00:29:41 +0100 Subject: [PATCH 04/39] [2.2.12] Bump version --- CHANGELOG.md | 2 +- dependencies.list | 2 +- package.json | 3 +-- src/RealmJS.xcodeproj/project.pbxproj | 4 ++-- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ae986e4..b71da7bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -2.2.11 Release notes (2018-2-22) +2.2.12 Release notes (2018-2-23) ============================================================= ### Breaking changes * None. diff --git a/dependencies.list b/dependencies.list index f99f8f60..c2d78ff7 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js -VERSION=2.2.11 +VERSION=2.2.12 REALM_CORE_VERSION=5.1.2 REALM_SYNC_VERSION=2.2.12 REALM_OBJECT_SERVER_VERSION=2.5.1 diff --git a/package.json b/package.json index 15e44ed3..fdba63dc 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": "2.2.11", + "version": "2.2.12", "license": "Apache-2.0", "homepage": "https://realm.io", "keywords": [ @@ -38,7 +38,6 @@ "scripts", "src", "tests", - "vendor", "binding.gyp", "realm.gypi", "target_defaults.gypi", diff --git a/src/RealmJS.xcodeproj/project.pbxproj b/src/RealmJS.xcodeproj/project.pbxproj index 4204d9dc..67eab8d6 100644 --- a/src/RealmJS.xcodeproj/project.pbxproj +++ b/src/RealmJS.xcodeproj/project.pbxproj @@ -961,7 +961,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2.2.11; + CURRENT_PROJECT_VERSION = 2.2.12; CXX = "$(SRCROOT)/../scripts/ccache-clang++.sh"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -1025,7 +1025,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2.2.11; + CURRENT_PROJECT_VERSION = 2.2.12; CXX = "$(SRCROOT)/../scripts/ccache-clang++.sh"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; From 46d128b444c86cc261cf41715ec95a2e29bda6ab Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Fri, 23 Feb 2018 00:33:13 +0100 Subject: [PATCH 05/39] Oops, shouldn't remove --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index fdba63dc..b77e28c1 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "react-native", "scripts", "src", + "vendor", "tests", "binding.gyp", "realm.gypi", From 4bc69f0a1b2ed75c8a191369500fb700ccceef92 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Fri, 23 Feb 2018 11:28:02 +0100 Subject: [PATCH 06/39] Don't include the downloaded files in package (#1672) --- vendor/.npmignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vendor/.npmignore b/vendor/.npmignore index ba5345a4..34c5959c 100644 --- a/vendor/.npmignore +++ b/vendor/.npmignore @@ -12,3 +12,5 @@ GCDWebServer/iOS GCDWebServer/Mac GCDWebServer/Tests GCDWebServer/tvOS + +realm-* From 76acf0ab60691a44945d092092043f35a72a3b2a Mon Sep 17 00:00:00 2001 From: astigsen Date: Tue, 27 Feb 2018 14:30:31 -0800 Subject: [PATCH 07/39] Updated query docs in API ref to reflect the features of the new query parser --- docs/tutorials/query-language.md | 58 ++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/docs/tutorials/query-language.md b/docs/tutorials/query-language.md index a7a093e8..35e9aeb6 100644 --- a/docs/tutorials/query-language.md +++ b/docs/tutorials/query-language.md @@ -23,7 +23,7 @@ let merlots = wines.filtered('variety == $0 && vintage <= $1', 'Merlot', maxYear ``` -### Relational operators +### Conditional operators You can use equality comparison on all property types: `==` and `!=` @@ -35,7 +35,7 @@ Example: let oldContacts = realm.objects('Contact').filtered('age > 2'); ``` -Note that for boolean properties, you should test against the expected keyword. +Note that for boolean properties, you should test against `true` or `false`. Example: ```JS @@ -43,7 +43,7 @@ let women = realm.objects('Contact').filtered('isMale == false'); ``` ### String operators -For string properties, prefix, suffix, and substring queries are supported by using the `BEGINSWITH`, `ENDSWITH`, and `CONTAINS` operators. +For string properties, prefix, suffix, and substring queries are supported by using the `BEGINSWITH`, `ENDSWITH`, `CONTAINS` and `LIKE` operators. For any string operation you can append `[c]` to the operator to make it case insensitive. @@ -53,6 +53,58 @@ let peopleWhoseNameContainsA = realm.objects('Contact').filtered('name CONTAINS[ let Johns = realm.objects('Contact').filtered('name ==[c] "john"'); ``` +You can do simple wildcard matching with `LIKE` which supports using `?` to match a single character and `*` to match zero or multiple characters. + +Example: +```JS +// Matches "John" and "Johnny" +let Johns = realm.objects('Contact').filtered('name LIKE "John*"'); +``` + ### Composition Use parentheses and the `&&`/`AND` and `||`/`OR` operators to compose queries. You can negate a predicate with `!`/`NOT`. +### Queries on collections + +When objects contain lists you can query into them using the collection operators `ANY`, `ALL` and `NONE`. + +Example: +```JS +// Find contacts with one or more teenage friends +let teens = realm.objects('Contact').filtered('ANY friends.age < 14'); + +// Find contacts where all friends are older than 21 +let adults = realm.objects('Contact').filtered('ALL friends.age > 21'); +``` + +You can query on aggregates over properties in the lists using the aggregate operators `.@count`, `.@avg`, `.@min`, `.@max` and `.@sum`. + +Example: +```JS +// Find contacts without friends +let teens = realm.objects('Contact').filtered('friends.@count == 0'); + +// Find contacts where the average age of their friends is above 40 +let adults = realm.objects('Contact').filtered('friends.@avg.age > 40"'); +``` + +Subqueries using the `SUBQUERY` operator allows you to filter the lists across multiple parameters while querying them. + +Example: +```JS +// Find contacts with friends above 21 in SF +let teens = realm.objects('Contact').filtered('SUBQUERY(friends, $friend, $friend.age > 21 AND $friend.city = 'SF').@count > 0'); +``` + +### Backlink queries + +Other objects can link to an object and you can query on that releationship using the `@links` and `@links.ClassName.PropertyName` syntax: + +Example: +```JS +// Find contacts with no incomming links +let lonely = realm.objects('Contact').filtered('@links.@count == 0); + +// Find contacts where someone from SF has them as friends +realm.objects('Contact').filtered('@links.Contact.friends.city == "SF"); +``` From 02ba8183e137e12b0aad9bf5beda32d8acc244be Mon Sep 17 00:00:00 2001 From: astigsen Date: Tue, 27 Feb 2018 14:33:11 -0800 Subject: [PATCH 08/39] Fixed missing chars --- docs/tutorials/query-language.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorials/query-language.md b/docs/tutorials/query-language.md index 35e9aeb6..f34702f2 100644 --- a/docs/tutorials/query-language.md +++ b/docs/tutorials/query-language.md @@ -103,8 +103,8 @@ Other objects can link to an object and you can query on that releationship usin Example: ```JS // Find contacts with no incomming links -let lonely = realm.objects('Contact').filtered('@links.@count == 0); +let lonely = realm.objects('Contact').filtered('@links.@count == 0'); // Find contacts where someone from SF has them as friends -realm.objects('Contact').filtered('@links.Contact.friends.city == "SF"); +realm.objects('Contact').filtered('@links.Contact.friends.city == "SF"'); ``` From d1df6d86f834b7125eeb43bd728fb3bf24c7dfc1 Mon Sep 17 00:00:00 2001 From: astigsen Date: Tue, 27 Feb 2018 16:37:45 -0800 Subject: [PATCH 09/39] Minor review fixes --- docs/tutorials/query-language.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/tutorials/query-language.md b/docs/tutorials/query-language.md index f34702f2..3677ede0 100644 --- a/docs/tutorials/query-language.md +++ b/docs/tutorials/query-language.md @@ -82,10 +82,10 @@ You can query on aggregates over properties in the lists using the aggregate ope Example: ```JS // Find contacts without friends -let teens = realm.objects('Contact').filtered('friends.@count == 0'); +let lonely = realm.objects('Contact').filtered('friends.@count == 0'); // Find contacts where the average age of their friends is above 40 -let adults = realm.objects('Contact').filtered('friends.@avg.age > 40"'); +let adults = realm.objects('Contact').filtered('friends.@avg.age > 40'); ``` Subqueries using the `SUBQUERY` operator allows you to filter the lists across multiple parameters while querying them. @@ -93,7 +93,7 @@ Subqueries using the `SUBQUERY` operator allows you to filter the lists across m Example: ```JS // Find contacts with friends above 21 in SF -let teens = realm.objects('Contact').filtered('SUBQUERY(friends, $friend, $friend.age > 21 AND $friend.city = 'SF').@count > 0'); +let teens = realm.objects('Contact').filtered('SUBQUERY(friends, $friend, $friend.age > 21 AND $friend.city = "SF").@count > 0'); ``` ### Backlink queries From 2f2c557cc16f31d6719c35ac7ab7369ac412f668 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Thu, 1 Mar 2018 15:37:44 +0100 Subject: [PATCH 10/39] [2.3.0-alpha.14] Bump version --- CHANGELOG.md | 6 +++--- dependencies.list | 6 +++--- package.json | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a23d782e..c8c427cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -2.3.0 Release notes (2018-2-19) +2.3.0 Release notes (2018-3-1) ============================================================= ### Breaking changes * [Sync] Sync protocol changed to version 24. @@ -18,9 +18,9 @@ * [Sync] Added class `Realm.Sync.Subscription` to support partial synced Realms. ### Internal -* Updated to Realm Core 5.2.0. +* Updated to Realm Core 5.8.0. * Updated to Realm Sync 3.0.0-beta.9. -* Tested against Realm Object Server 3.0.0-alpha.2. +* Tested against Realm Object Server 3.0.0-alpha.8. 2.2.10 Release notes (2018-2-20) diff --git a/dependencies.list b/dependencies.list index df28f8a4..8e1d7708 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js -VERSION=2.3.0-alpha.13 -REALM_CORE_VERSION=5.2.0 +VERSION=2.3.0-alpha.14 +REALM_CORE_VERSION=5.3.0 REALM_SYNC_VERSION=3.0.0-beta.9 -REALM_OBJECT_SERVER_VERSION=3.0.0-alpha.2 +REALM_OBJECT_SERVER_VERSION=3.0.0-alpha.8 diff --git a/package.json b/package.json index b08bb4a2..4fa0a72a 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": "2.3.0-alpha.13", + "version": "2.3.0-alpha.14", "license": "Apache-2.0", "homepage": "https://realm.io", "keywords": [ From 27349fc0b9c1dd34dc4366084265af63000fb116 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Thu, 1 Mar 2018 15:38:26 +0100 Subject: [PATCH 11/39] [2.3.0-alpha.14] Bump version --- scripts/test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/test.sh b/scripts/test.sh index 16832ea2..f4bd0784 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -211,7 +211,7 @@ if [[ -z "$(command -v nvm)" ]]; then set -e fi if [[ "$(command -v nvm)" ]]; then - nvm install 7.10.0 + nvm install 6.11.3 fi # Remove cached packages From 9698ea651d7abc6038cc454046743a2215b92aeb Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Thu, 1 Mar 2018 16:38:47 +0100 Subject: [PATCH 12/39] Update --- src/object-store | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/object-store b/src/object-store index d5fe9a62..bb559df9 160000 --- a/src/object-store +++ b/src/object-store @@ -1 +1 @@ -Subproject commit d5fe9a626c0adc0345d128201e0df8bb4004725d +Subproject commit bb559df9237ece49f9c889993f7c1aff619b48f9 From e93ff333eded7261d86e53372bbc2f3063627522 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 1 Mar 2018 20:30:57 +0000 Subject: [PATCH 13/39] [2.3.0-alpha.15] Bump version --- dependencies.list | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dependencies.list b/dependencies.list index 8e1d7708..a87aea2f 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js -VERSION=2.3.0-alpha.14 +VERSION=2.3.0-alpha.15 REALM_CORE_VERSION=5.3.0 -REALM_SYNC_VERSION=3.0.0-beta.9 +REALM_SYNC_VERSION=3.0.0-beta.10 REALM_OBJECT_SERVER_VERSION=3.0.0-alpha.8 diff --git a/package.json b/package.json index 4fa0a72a..eedaa630 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": "2.3.0-alpha.14", + "version": "2.3.0-alpha.15", "license": "Apache-2.0", "homepage": "https://realm.io", "keywords": [ From d89c7ea24f4b7062650a733410a57ac671475ae4 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Wed, 28 Feb 2018 11:29:53 -0800 Subject: [PATCH 14/39] Delete a helper function that's now exposed directly by sync --- src/js_realm.hpp | 32 +------------------------------- 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/src/js_realm.hpp b/src/js_realm.hpp index 961032a7..72ce3b31 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -1002,36 +1002,6 @@ void RealmClass::compact(ContextType ctx, ObjectType this_object, Arguments a return_value.set(realm->compact()); } -#if REALM_ENABLE_SYNC -namespace { - -// FIXME: Sync should provide this: https://github.com/realm/realm-sync/issues/1796 -inline sync::ObjectID object_id_from_string(std::string const& string) -{ - if (string.front() != '{' || string.back() != '}') - throw std::invalid_argument("Invalid object ID."); - - size_t dash_index = string.find('-'); - if (dash_index == std::string::npos) - throw std::invalid_argument("Invalid object ID."); - - std::string high_string = string.substr(1, dash_index - 1); - std::string low_string = string.substr(dash_index + 1, string.size() - dash_index - 2); - - if (high_string.size() == 0 || high_string.size() > 16 || low_string.size() == 0 || low_string.size() > 16) - throw std::invalid_argument("Invalid object ID."); - - auto isxdigit = static_cast(std::isxdigit); - if (!std::all_of(high_string.begin(), high_string.end(), isxdigit) || - !std::all_of(low_string.begin(), low_string.end(), isxdigit)) { - throw std::invalid_argument("Invalid object ID."); - } - return sync::ObjectID(strtoull(high_string.c_str(), nullptr, 16), strtoull(low_string.c_str(), nullptr, 16)); -} - -} // unnamed namespace -#endif // REALM_ENABLE_SYNC - template void RealmClass::object_for_object_id(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue& return_value) { args.validate_count(2); @@ -1045,7 +1015,7 @@ void RealmClass::object_for_object_id(ContextType ctx, ObjectType this_object validated_object_schema_for_value(ctx, realm, args[0], object_type); std::string object_id_string = Value::validated_to_string(ctx, args[1]); - auto object_id = object_id_from_string(object_id_string); + auto object_id = sync::ObjectID::from_string(object_id_string); const Group& group = realm->read_group(); size_t ndx = sync::row_for_object_id(group, *ObjectStore::table_for_object_type(group, object_type), object_id); From 4722e75eba64273fd7a68a69291f65150a964d6e Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Wed, 28 Feb 2018 12:45:58 -0800 Subject: [PATCH 15/39] Expose computed privileges --- src/js_realm.hpp | 77 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 60 insertions(+), 17 deletions(-) diff --git a/src/js_realm.hpp b/src/js_realm.hpp index 72ce3b31..6fc662df 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -186,6 +186,7 @@ public: static void compact(ContextType, ObjectType, Arguments, ReturnValue &); static void delete_model(ContextType, ObjectType, Arguments, ReturnValue &); static void object_for_object_id(ContextType, ObjectType, Arguments, ReturnValue&); + static void privileges(ContextType, ObjectType, Arguments, ReturnValue&); // properties static void get_empty(ContextType, ObjectType, ReturnValue &); @@ -242,6 +243,7 @@ public: {"close", wrap}, {"compact", wrap}, {"deleteModel", wrap}, + {"privileges", wrap}, {"_objectForObjectId", wrap}, #if REALM_ENABLE_SYNC {"_waitForDownload", wrap}, @@ -291,7 +293,8 @@ public: return name; } - static const ObjectSchema& validated_object_schema_for_value(ContextType ctx, const SharedRealm &realm, const ValueType &value, std::string& object_type) { + static const ObjectSchema& validated_object_schema_for_value(ContextType ctx, const SharedRealm &realm, const ValueType &value) { + std::string object_type; if (Value::is_constructor(ctx, value)) { FunctionType constructor = Value::to_constructor(ctx, value); @@ -541,7 +544,7 @@ void RealmClass::constructor(ContextType ctx, ObjectType this_object, size_t template SharedRealm RealmClass::create_shared_realm(ContextType ctx, realm::Realm::Config config, bool schema_updated, - ObjectDefaultsMap && defaults, ConstructorMap && constructors) { + ObjectDefaultsMap&& defaults, ConstructorMap&& constructors) { config.execution_context = Context::get_execution_context_id(ctx); SharedRealm realm; @@ -551,9 +554,6 @@ SharedRealm RealmClass::create_shared_realm(ContextType ctx, realm::Realm::Co catch (const RealmFileException& ex) { handleRealmFileException(ctx, config, ex); } - catch (...) { - throw; - } GlobalContextType global_context = Context::get_global_context(ctx); if (!realm->m_binding_context) { @@ -790,10 +790,8 @@ void RealmClass::objects(ContextType ctx, ObjectType this_object, Arguments a args.validate_maximum(1); SharedRealm realm = *get_internal>(this_object); - std::string object_type; - validated_object_schema_for_value(ctx, realm, args[0], object_type); - - return_value.set(ResultsClass::create_instance(ctx, realm, object_type)); + auto& object_schema = validated_object_schema_for_value(ctx, realm, args[0]); + return_value.set(ResultsClass::create_instance(ctx, realm, object_schema.name)); } template @@ -802,7 +800,7 @@ void RealmClass::object_for_primary_key(ContextType ctx, ObjectType this_obje SharedRealm realm = *get_internal>(this_object); std::string object_type; - auto &object_schema = validated_object_schema_for_value(ctx, realm, args[0], object_type); + auto &object_schema = validated_object_schema_for_value(ctx, realm, args[0]); NativeAccessor accessor(ctx, realm, object_schema); auto realm_object = realm::Object::get_for_primary_key(accessor, realm, object_schema, args[1]); @@ -820,8 +818,7 @@ void RealmClass::create(ContextType ctx, ObjectType this_object, Arguments ar SharedRealm realm = *get_internal>(this_object); realm->verify_open(); - std::string object_type; - auto &object_schema = validated_object_schema_for_value(ctx, realm, args[0], object_type); + auto &object_schema = validated_object_schema_for_value(ctx, realm, args[0]); ObjectType object = Value::validated_to_object(ctx, args[1], "properties"); if (Value::is_array(ctx, args[1])) { @@ -1011,21 +1008,67 @@ void RealmClass::object_for_object_id(ContextType ctx, ObjectType this_object if (!sync::has_object_ids(realm->read_group())) throw std::logic_error("Realm._objectForObjectId() can only be used with synced Realms."); - std::string object_type = Value::validated_to_string(ctx, args[0]); - validated_object_schema_for_value(ctx, realm, args[0], object_type); - + auto& object_schema = validated_object_schema_for_value(ctx, realm, args[0]); std::string object_id_string = Value::validated_to_string(ctx, args[1]); auto object_id = sync::ObjectID::from_string(object_id_string); const Group& group = realm->read_group(); - size_t ndx = sync::row_for_object_id(group, *ObjectStore::table_for_object_type(group, object_type), object_id); + size_t ndx = sync::row_for_object_id(group, *ObjectStore::table_for_object_type(group, object_schema.name), object_id); if (ndx != realm::npos) { - return_value.set(RealmObjectClass::create_instance(ctx, realm::Object(realm, object_type, ndx))); + return_value.set(RealmObjectClass::create_instance(ctx, realm::Object(realm, object_schema.name, ndx))); } #else throw std::logic_error("Realm._objectForObjectId() can only be used with synced Realms."); #endif // REALM_ENABLE_SYNC } +template +void RealmClass::privileges(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(1); + + using Privilege = realm::ComputedPrivileges; + auto has_privilege = [](Privilege actual, Privilege expected) { + return (static_cast(actual) & static_cast(expected)) == static_cast(expected); + }; + + SharedRealm realm = *get_internal>(this_object); + if (args.count == 0) { + auto p = realm->get_privileges(); + ObjectType object = Object::create_empty(ctx); + Object::set_property(ctx, object, "read", Value::from_boolean(ctx, has_privilege(p, Privilege::Read))); + Object::set_property(ctx, object, "update", Value::from_boolean(ctx,has_privilege(p, Privilege::Update))); + Object::set_property(ctx, object, "modifySchema", Value::from_boolean(ctx, has_privilege(p, Privilege::ModifySchema))); + Object::set_property(ctx, object, "setPermissions", Value::from_boolean(ctx, has_privilege(p, Privilege::SetPermissions))); + return_value.set(object); + return; + } + + if (Value::is_object(ctx, args[0])) { + auto arg = Value::to_object(ctx, args[0]); + if (Object::template is_instance>(ctx, arg)) { + auto obj = get_internal>(arg); + auto p = realm->get_privileges(obj->row()); + + ObjectType object = Object::create_empty(ctx); + Object::set_property(ctx, object, "read", Value::from_boolean(ctx, has_privilege(p, Privilege::Read))); + Object::set_property(ctx, object, "update", Value::from_boolean(ctx,has_privilege(p, Privilege::Update))); + Object::set_property(ctx, object, "delete", Value::from_boolean(ctx,has_privilege(p, Privilege::Delete))); + Object::set_property(ctx, object, "setPermissions", Value::from_boolean(ctx, has_privilege(p, Privilege::SetPermissions))); + return_value.set(object); + return; + } + } + + auto& object_schema = validated_object_schema_for_value(ctx, realm, args[0]); + auto p = realm->get_privileges(object_schema.name); + ObjectType object = Object::create_empty(ctx); + Object::set_property(ctx, object, "read", Value::from_boolean(ctx, has_privilege(p, Privilege::Read))); + Object::set_property(ctx, object, "update", Value::from_boolean(ctx,has_privilege(p, Privilege::Update))); + Object::set_property(ctx, object, "create", Value::from_boolean(ctx, has_privilege(p, Privilege::Create))); + Object::set_property(ctx, object, "subscribe", Value::from_boolean(ctx, has_privilege(p, Privilege::Query))); + Object::set_property(ctx, object, "setPermissions", Value::from_boolean(ctx, has_privilege(p, Privilege::SetPermissions))); + return_value.set(object); +} + } // js } // realm From ac7f7d9c4ec2cd520afb4708d43bf59ec0dc9a68 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Wed, 28 Feb 2018 14:56:00 -0800 Subject: [PATCH 16/39] Add schema definitions for the permissions types --- lib/extensions.js | 62 +++++++++++++++++++++++++++++++++++++++++++++++ lib/index.d.ts | 27 +++++++++++++++++++++ src/object-store | 2 +- 3 files changed, 90 insertions(+), 1 deletion(-) diff --git a/lib/extensions.js b/lib/extensions.js index 5c793e34..033d305c 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -167,6 +167,68 @@ module.exports = function(realmConstructor) { Complete: 1, // The subscription has been processed by the sync server and data is being synced to the device. Invalidated: 3, // The subscription has been removed. }; + + // Define the permission schemas as constructors so that they can be + // passed into directly to functions which want object type names + const permissionsSchema = Object.freeze({ + Class: function() {}, + Permission: function() {}, + Realm: function() {}, + Role: function() {}, + User: function() {}, + }); + permissionsSchema.Permission.schema = Object.freeze({ + name: '__Permission', + properties: { + role: '__Role', + canRead: {type: 'bool', default: false}, + canUpdate: {type: 'bool', default: false}, + canDelete: {type: 'bool', default: false}, + canSetPermissions: {type: 'bool', default: false}, + canQuery: {type: 'bool', default: false}, + canCreate: {type: 'bool', default: false}, + canModifySchema: {type: 'bool', default: false}, + } + }); + + permissionsSchema.User.schema = Object.freeze({ + name: '__User', + primaryKey: 'id', + properties: { + id: 'string' + } + }); + + permissionsSchema.Role.schema = Object.freeze({ + name: '__Role', + primaryKey: 'name', + properties: { + name: 'string', + members: '__User[]' + } + }); + + permissionsSchema.Class.schema = Object.freeze({ + name: '__Class', + primaryKey: 'class_name', + properties: { + class_name: 'string', + permissions: '__Permission[]' + } + }); + + permissionsSchema.Realm.schema = Object.freeze({ + name: '__Realm', + primaryKey: 'id', + properties: { + id: 'int', + permissions: '__Permission[]' + } + }); + Object.defineProperty(realmConstructor, 'Permissions', { + value: permissionsSchema, + configurable: false + }); } // TODO: Remove this now useless object. diff --git a/lib/index.d.ts b/lib/index.d.ts index 76cd5432..490b43fa 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -622,6 +622,33 @@ declare class Realm { compact(): boolean; } +declare namespace Realm.Permissions { + interface Permission { + identity: string; + canRead: boolean; + canUpdate: boolean; + canDelete: boolean; + canSetPermissions: boolean; + canQuery: boolean; + canCreate: boolean; + canModifySchema: boolean; + } + interface User { + identity: string; + } + interface Role { + name: string; + members: User[]; + } + interface Class { + class_name: string; + permissions: Permission[]; + } + interface Realm { + permissions: Permission[]; + } +} + declare module 'realm' { export = Realm } diff --git a/src/object-store b/src/object-store index bb559df9..39c133a9 160000 --- a/src/object-store +++ b/src/object-store @@ -1 +1 @@ -Subproject commit bb559df9237ece49f9c889993f7c1aff619b48f9 +Subproject commit 39c133a9aff6feaa76e9631696bdf67bc57ed5d5 From 0047ffba13f6cb34e299123e370a947a7abc1c58 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Thu, 1 Mar 2018 11:54:56 -0800 Subject: [PATCH 17/39] Add object-level permissions tests --- src/js_realm.hpp | 1 - tests/js/asserts.js | 3 +- tests/js/permission-tests.js | 93 ++++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 2 deletions(-) diff --git a/src/js_realm.hpp b/src/js_realm.hpp index 6fc662df..9e6da957 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -35,7 +35,6 @@ #include "js_sync.hpp" #include "sync/sync_config.hpp" #include "sync/sync_manager.hpp" -#include "sync/partial_sync.hpp" #endif #include "shared_realm.hpp" diff --git a/tests/js/asserts.js b/tests/js/asserts.js index ff01df46..154c4821 100644 --- a/tests/js/asserts.js +++ b/tests/js/asserts.js @@ -37,7 +37,8 @@ module.exports = { } else if (type === 'object') { for (const key of Object.keys(val1)) { - this.assertEqual(val1[key], val2[key], errorMessage, depth + 1); + const message = errorMessage ? `${errorMessage}: ${key}` : key; + this.assertEqual(val1[key], val2[key], message, depth + 1); } } else if (type === 'list') { diff --git a/tests/js/permission-tests.js b/tests/js/permission-tests.js index 4379e494..8b849005 100644 --- a/tests/js/permission-tests.js +++ b/tests/js/permission-tests.js @@ -57,6 +57,34 @@ function repeatUntil(fn, predicate) { return check; } +function subscribe(results) { + const subscription = results.subscribe() + return new Promise((resolve, reject) => { + subscription.addListener((subscription, state) => { + if (state == Realm.Sync.SubscriptionState.Complete) { + resolve(); + } + else if (state == Realm.Sync.SubscriptionState.Error) { + reject(); + } + }); + setTimeout(() => reject("listener never called"), 5000); + }); +} + +function waitForUpload(realm) { + let session = realm.syncSession; + return new Promise(resolve => { + let callback = (transferred, total) => { + if (transferred === total) { + session.removeProgressNotification(callback); + resolve(realm); + } + } + session.addProgressNotification('upload', 'forCurrentlyOutstandingWork', callback); + }); +} + module.exports = { testApplyAndGetGrantedPermissions() { return createUsersWithTestRealms(1) @@ -118,5 +146,70 @@ module.exports = { } }); }, + + testObjectPermissions() { + let config = (user, url) => { + return { + schema: [ + Realm.Permissions.Permission, + Realm.Permissions.User, + Realm.Permissions.Role, + { + name: 'Object', + properties: { + value: 'int', + permissions: '__Permission[]' + } + } + ], + sync: {user, url, partial: true} + }; + }; + let owner, otherUser + return Realm.Sync.User.register('http://localhost:9080', uuid(), 'password') + .then(user => { + owner = user; + new Realm({sync: {user, url: 'realm://localhost:9080/~/test'}}).close(); + return Realm.Sync.User.register('http://localhost:9080', uuid(), 'password') + }) + .then(user => { + otherUser = user; + return owner.applyPermissions({userId: otherUser.identity}, `/${owner.identity}/test`, 'read') + }) + .then(() => { + let realm = new Realm(config(owner, 'realm://localhost:9080/~/test')); + realm.write(() => { + let user = realm.create(Realm.Permissions.User, {id: otherUser.identity}) + let role = realm.create(Realm.Permissions.Role, {name: 'reader'}) + role.members.push(user) + + let obj1 = realm.create('Object', {value: 1}) + let obj2 = realm.create('Object', {value: 2}) + obj2.permissions.push(realm.create(Realm.Permissions.Permission, + {role: role, canRead: true, canUpdate: false})) + }); + return waitForUpload(realm).then(() => realm.close()); + }) + .then(() => Realm.open(config(otherUser, `realm://localhost:9080/${owner.identity}/test`))) + .then((realm) => subscribe(realm.objects('Object')).then(() => realm)) + .then((realm) => { + // Should have full access to the Realm as a whole + TestCase.assertSimilar('object', realm.privileges(), + {read: true, update: true, modifySchema: true, setPermissions: true}); + TestCase.assertSimilar('object', realm.privileges('Object'), + {read: true, update: true, create: true, subscribe: true, setPermissions: true}); + // Verify that checking via constructor works too + TestCase.assertSimilar('object', realm.privileges(Realm.Permissions.User), + {read: true, update: true, create: true, subscribe: true, setPermissions: true}); + + // Should only be able to see the second object + let results = realm.objects('Object') + TestCase.assertEqual(results.length, 1); + TestCase.assertEqual(results[0].value, 2); + TestCase.assertSimilar('object', realm.privileges(results[0]), + {read: true, update: false, delete: false, setPermissions: false}); + realm.close(); + }); + } } From 3934413180374e7d55fd909330573f2f4109dea6 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Fri, 2 Mar 2018 10:07:29 -0800 Subject: [PATCH 18/39] Update object store --- src/object-store | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/object-store b/src/object-store index 39c133a9..b250563e 160000 --- a/src/object-store +++ b/src/object-store @@ -1 +1 @@ -Subproject commit 39c133a9aff6feaa76e9631696bdf67bc57ed5d5 +Subproject commit b250563ea1eb9f32ec7dbd76f2c6f8f1a26914cc From 3eaef14fca7dcc9a31a3e36bdea04707e50a7928 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Fri, 2 Mar 2018 19:43:06 +0100 Subject: [PATCH 19/39] Updating to Realm Sync 2.2.14 (#1683) --- CHANGELOG.md | 15 +++++++++++++++ dependencies.list | 2 +- scripts/test.sh | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b71da7bf..5c4ed4ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +X.Y.Z Release notes +============================================================= +### Breaking changes +* None. + +### Enhancements +* None. + +### Bug fixes +* [Object Server] Fixed handling of SSL certificates for the sync client. + +### Internal +* Updated to Realm Sync 2.2.14. + + 2.2.12 Release notes (2018-2-23) ============================================================= ### Breaking changes diff --git a/dependencies.list b/dependencies.list index c2d78ff7..c9131da4 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js VERSION=2.2.12 REALM_CORE_VERSION=5.1.2 -REALM_SYNC_VERSION=2.2.12 +REALM_SYNC_VERSION=2.2.14 REALM_OBJECT_SERVER_VERSION=2.5.1 diff --git a/scripts/test.sh b/scripts/test.sh index 16832ea2..f4bd0784 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -211,7 +211,7 @@ if [[ -z "$(command -v nvm)" ]]; then set -e fi if [[ "$(command -v nvm)" ]]; then - nvm install 7.10.0 + nvm install 6.11.3 fi # Remove cached packages From 61126aef5329dabf39e4c8f1926050c2db75b7d0 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Fri, 2 Mar 2018 10:47:04 -0800 Subject: [PATCH 20/39] Use node.execPath in runOutOfProcess() --- tests/js/session-tests.js | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/tests/js/session-tests.js b/tests/js/session-tests.js index 24a47288..a85410fa 100644 --- a/tests/js/session-tests.js +++ b/tests/js/session-tests.js @@ -61,27 +61,23 @@ function copyFileToTempDir(filename) { return tmpFile.name; } -function runOutOfProcess(nodeJsFilePath) { - var nodeArgs = Array.prototype.slice.call(arguments); +function runOutOfProcess() { + const args = Array.prototype.slice.call(arguments); let tmpDir = tmp.dirSync(); - let content = fs.readFileSync(nodeJsFilePath, 'utf8'); - let tmpFile = tmp.fileSync({ dir: tmpDir.name }); - fs.appendFileSync(tmpFile.fd, content, { encoding: 'utf8' }); - nodeArgs[0] = tmpFile.name; + console.log(`runOutOfProcess : ${args.join(' ')}`); return new Promise((resolve, reject) => { try { - console.log('runOutOfProcess command\n node ' + nodeArgs.join(" ")); - const child = execFile('node', nodeArgs, { cwd: tmpDir.name }, (error, stdout, stderr) => { + execFile(process.execPath, args, {cwd: tmpDir.name}, (error, stdout, stderr) => { if (error) { - console.error("runOutOfProcess failed\n" + error); - reject(new Error(`Running ${nodeJsFilePath} failed. error: ${error}`)); + console.error("runOutOfProcess failed\n", error); + reject(new Error(`Running ${modulePath} failed. error: ${error}`)); return; } console.log('runOutOfProcess success\n' + stdout); resolve(); }); - } + } catch (e) { reject(e); } From e34ec308e3c934623318a98df14ba6023e426529 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Fri, 2 Mar 2018 20:44:00 +0100 Subject: [PATCH 21/39] [2.2.13] Bump version --- CHANGELOG.md | 2 +- dependencies.list | 2 +- package.json | 2 +- src/RealmJS.xcodeproj/project.pbxproj | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c4ed4ed..22e33f53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -X.Y.Z Release notes +2.2.13 Release notes (2018-3-2) ============================================================= ### Breaking changes * None. diff --git a/dependencies.list b/dependencies.list index c9131da4..be864702 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js -VERSION=2.2.12 +VERSION=2.2.13 REALM_CORE_VERSION=5.1.2 REALM_SYNC_VERSION=2.2.14 REALM_OBJECT_SERVER_VERSION=2.5.1 diff --git a/package.json b/package.json index b77e28c1..09c5295f 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": "2.2.12", + "version": "2.2.13", "license": "Apache-2.0", "homepage": "https://realm.io", "keywords": [ diff --git a/src/RealmJS.xcodeproj/project.pbxproj b/src/RealmJS.xcodeproj/project.pbxproj index 67eab8d6..b972afbf 100644 --- a/src/RealmJS.xcodeproj/project.pbxproj +++ b/src/RealmJS.xcodeproj/project.pbxproj @@ -961,7 +961,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2.2.12; + CURRENT_PROJECT_VERSION = 2.2.13; CXX = "$(SRCROOT)/../scripts/ccache-clang++.sh"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -1025,7 +1025,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2.2.12; + CURRENT_PROJECT_VERSION = 2.2.13; CXX = "$(SRCROOT)/../scripts/ccache-clang++.sh"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; From 302db024bfe8b8bb50fda3381ff624731e0d0839 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Fri, 2 Mar 2018 12:05:09 -0800 Subject: [PATCH 22/39] Eliminate some gratuitous promise nesting in tests --- tests/js/session-tests.js | 628 ++++++++++++++++++-------------------- 1 file changed, 299 insertions(+), 329 deletions(-) diff --git a/tests/js/session-tests.js b/tests/js/session-tests.js index a85410fa..1ef8a21c 100644 --- a/tests/js/session-tests.js +++ b/tests/js/session-tests.js @@ -271,43 +271,40 @@ module.exports = { const expectedObjectsCount = 3; return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) - .then(() => { - return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { - return new Promise((resolve, reject) => { - const accessTokenRefreshed = this; - let successCounter = 0; + .then(() => Realm.Sync.User.login('http://localhost:9080', username, 'password')) + .then(user => { + const accessTokenRefreshed = this; + let successCounter = 0; - let config = { - sync: { user, url: `realm://localhost:9080/~/${realmName}` } - }; + let config = { + sync: { user, url: `realm://localhost:9080/~/${realmName}` } + }; + return new Promise((resolve, reject) => { + Realm.openAsync(config, (error, realm) => { + if (error) { + reject(error); + return; + } + try { + let actualObjectsCount = realm.objects('Dog').length; + TestCase.assertEqual(actualObjectsCount, expectedObjectsCount, "Synced realm does not contain the expected objects count"); - Realm.openAsync(config, (error, realm) => { - try { - if (error) { - reject(error); - } + let firstDog = realm.objects('Dog')[0]; + TestCase.assertTrue(({}).hasOwnProperty.call(firstDog, 'name'), "Synced realm does not have an inffered schema"); + TestCase.assertTrue(firstDog.name, "Synced realm object's property should have a value"); + TestCase.assertTrue(firstDog.name.indexOf('Lassy') !== -1, "Synced realm object's property should contain the actual written value"); - let actualObjectsCount = realm.objects('Dog').length; - TestCase.assertEqual(actualObjectsCount, expectedObjectsCount, "Synced realm does not contain the expected objects count"); - - let firstDog = realm.objects('Dog')[0]; - TestCase.assertTrue(({}).hasOwnProperty.call(firstDog, 'name'), "Synced realm does not have an inffered schema"); - TestCase.assertTrue(firstDog.name, "Synced realm object's property should have a value"); - TestCase.assertTrue(firstDog.name.indexOf('Lassy') !== -1, "Synced realm object's property should contain the actual written value"); - - - const session = realm.syncSession; - TestCase.assertInstanceOf(session, Realm.Sync.Session); - TestCase.assertEqual(session.user.identity, user.identity); - TestCase.assertEqual(session.config.url, config.sync.url); - TestCase.assertEqual(session.config.user.identity, config.sync.user.identity); - TestCase.assertEqual(session.state, 'active'); - resolve(); - } - catch (e) { - reject(e); - } - }); + const session = realm.syncSession; + TestCase.assertInstanceOf(session, Realm.Sync.Session); + TestCase.assertEqual(session.user.identity, user.identity); + TestCase.assertEqual(session.config.url, config.sync.url); + TestCase.assertEqual(session.config.user.identity, config.sync.user.identity); + TestCase.assertEqual(session.state, 'active'); + resolve(); + } + catch (e) { + reject(e); + } }); }); }); @@ -317,26 +314,22 @@ module.exports = { const username = uuid(); const expectedObjectsCount = 3; + const accessTokenRefreshed = this; + let successCounter = 0; - return new Promise((resolve, reject) => { - const accessTokenRefreshed = this; - let successCounter = 0; + let config = { + schema: [{ name: 'Dog', properties: { name: 'string' } }], + }; - let config = { - schema: [{ name: 'Dog', properties: { name: 'string' } }], - }; + return Realm.open(config).then(realm => { + realm.write(() => { + for (let i = 1; i <= 3; i++) { + realm.create('Dog', { name: `Lassy ${i}` }); + } + }); - Realm.open(config).then(realm => { - realm.write(() => { - for (let i = 1; i <= 3; i++) { - realm.create('Dog', { name: `Lassy ${i}` }); - } - }); - - let actualObjectsCount = realm.objects('Dog').length; - TestCase.assertEqual(actualObjectsCount, expectedObjectsCount, "Local realm does not contain the expected objects count"); - resolve(); - }).catch(error => reject(error)); + let actualObjectsCount = realm.objects('Dog').length; + TestCase.assertEqual(actualObjectsCount, expectedObjectsCount, "Local realm does not contain the expected objects count"); }); }, @@ -408,37 +401,33 @@ module.exports = { const realmName = uuid(); return runOutOfProcess(__dirname + '/nested-list-helper.js', __dirname + '/schemas.js', username, realmName, REALM_MODULE_PATH) - .then(() => { - return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { - return new Promise((resolve, reject) => { - let config = { - schema: [schemas.ParentObject, schemas.NameObject], - sync: { user, url: `realm://localhost:9080/~/${realmName}` } - }; - Realm.open(config).then(realm => { - let objects = realm.objects('ParentObject'); + .then(() => Realm.Sync.User.login('http://localhost:9080', username, 'password')) + .then(user => { + let config = { + schema: [schemas.ParentObject, schemas.NameObject], + sync: { user, url: `realm://localhost:9080/~/${realmName}` } + }; + return Realm.open(config) + }).then(realm => { + let objects = realm.objects('ParentObject'); - let json = JSON.stringify(objects); - TestCase.assertEqual(json, '{"0":{"id":1,"name":{"0":{"family":"Larsen","given":{"0":"Hans","1":"Jørgen"},"prefix":{}},"1":{"family":"Hansen","given":{"0":"Ib"},"prefix":{}}}},"1":{"id":2,"name":{"0":{"family":"Petersen","given":{"0":"Gurli","1":"Margrete"},"prefix":{}}}}}'); - TestCase.assertEqual(objects.length, 2); - TestCase.assertEqual(objects[0].name.length, 2); - TestCase.assertEqual(objects[0].name[0].given.length, 2); - TestCase.assertEqual(objects[0].name[0].prefix.length, 0); - TestCase.assertEqual(objects[0].name[0].given[0], 'Hans'); - TestCase.assertEqual(objects[0].name[0].given[1], 'Jørgen') - TestCase.assertEqual(objects[0].name[1].given.length, 1); - TestCase.assertEqual(objects[0].name[1].given[0], 'Ib'); - TestCase.assertEqual(objects[0].name[1].prefix.length, 0); + let json = JSON.stringify(objects); + TestCase.assertEqual(json, '{"0":{"id":1,"name":{"0":{"family":"Larsen","given":{"0":"Hans","1":"Jørgen"},"prefix":{}},"1":{"family":"Hansen","given":{"0":"Ib"},"prefix":{}}}},"1":{"id":2,"name":{"0":{"family":"Petersen","given":{"0":"Gurli","1":"Margrete"},"prefix":{}}}}}'); + TestCase.assertEqual(objects.length, 2); + TestCase.assertEqual(objects[0].name.length, 2); + TestCase.assertEqual(objects[0].name[0].given.length, 2); + TestCase.assertEqual(objects[0].name[0].prefix.length, 0); + TestCase.assertEqual(objects[0].name[0].given[0], 'Hans'); + TestCase.assertEqual(objects[0].name[0].given[1], 'Jørgen') + TestCase.assertEqual(objects[0].name[1].given.length, 1); + TestCase.assertEqual(objects[0].name[1].given[0], 'Ib'); + TestCase.assertEqual(objects[0].name[1].prefix.length, 0); - TestCase.assertEqual(objects[1].name.length, 1); - TestCase.assertEqual(objects[1].name[0].given.length, 2); - TestCase.assertEqual(objects[1].name[0].prefix.length, 0); - TestCase.assertEqual(objects[1].name[0].given[0], 'Gurli'); - TestCase.assertEqual(objects[1].name[0].given[1], 'Margrete'); - resolve(); - }).catch(() => reject()); - }); - }); + TestCase.assertEqual(objects[1].name.length, 1); + TestCase.assertEqual(objects[1].name[0].given.length, 2); + TestCase.assertEqual(objects[1].name[0].prefix.length, 0); + TestCase.assertEqual(objects[1].name[0].given[0], 'Gurli'); + TestCase.assertEqual(objects[1].name[0].given[1], 'Margrete'); }); }, @@ -606,60 +595,59 @@ module.exports = { const realmName = uuid(); return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) - .then(() => { - return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { - return new Promise((resolve, reject) => { - let config = { - sync: { - user, - url: `realm://localhost:9080/~/${realmName}` - }, - schema: [{ name: 'Dog', properties: { name: 'string' } }], - }; + .then(() => Realm.Sync.User.login('http://localhost:9080', username, 'password')) + .then(user => { + let config = { + sync: { + user, + url: `realm://localhost:9080/~/${realmName}` + }, + schema: [{ name: 'Dog', properties: { name: 'string' } }], + }; - let realm = new Realm(config); - let unregisterFunc; + let realm = new Realm(config); + let unregisterFunc; - let writeDataFunc = () => { - realm.write(() => { - for (let i = 1; i <= 3; i++) { - realm.create('Dog', { name: `Lassy ${i}` }); - } - }); + let writeDataFunc = () => { + realm.write(() => { + for (let i = 1; i <= 3; i++) { + realm.create('Dog', { name: `Lassy ${i}` }); + } + }); + } + + return new Promise((resolve, reject) => { + let syncFinished = false; + let failOnCall = false; + const progressCallback = (transferred, total) => { + if (failOnCall) { + reject(new Error("Progress callback should not be called after removeProgressNotification")); } - let syncFinished = false; - let failOnCall = false; - const progressCallback = (transferred, total) => { - if (failOnCall) { - reject(new Error("Progress callback should not be called after removeProgressNotification")); - } + syncFinished = transferred === total; - syncFinished = transferred === total; + //unregister and write some new data. + if (syncFinished) { + failOnCall = true; + unregisterFunc(); - //unregister and write some new data. - if (syncFinished) { - failOnCall = true; - unregisterFunc(); + //use second callback to wait for sync finished + realm.syncSession.addProgressNotification('upload', 'reportIndefinitely', (transferred, transferable) => { + if (transferred === transferable) { + resolve(); + } + }); + writeDataFunc(); + } + }; - //use second callback to wait for sync finished - realm.syncSession.addProgressNotification('upload', 'reportIndefinitely', (transferred, transferable) => { - if (transferred === transferable) { - resolve(); - } - }); - writeDataFunc(); - } - }; + realm.syncSession.addProgressNotification('upload', 'reportIndefinitely', progressCallback); - realm.syncSession.addProgressNotification('upload', 'reportIndefinitely', progressCallback); + unregisterFunc = () => { + realm.syncSession.removeProgressNotification(progressCallback); + }; - unregisterFunc = () => { - realm.syncSession.removeProgressNotification(progressCallback); - }; - - writeDataFunc(); - }); + writeDataFunc(); }); }); }, @@ -671,36 +659,24 @@ module.exports = { const username = uuid(); const realmName = uuid(); + let progressCalled = false; return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) - .then(() => { - return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { - return new Promise((resolve, reject) => { - let config = { - sync: { - user, - url: `realm://localhost:9080/~/${realmName}` - }, - schema: [{ name: 'Dog', properties: { name: 'string' } }], - }; + .then(() => Realm.Sync.User.login('http://localhost:9080', username, 'password')) + .then(user => { + let config = { + sync: { + user, + url: `realm://localhost:9080/~/${realmName}` + }, + schema: [{ name: 'Dog', properties: { name: 'string' } }], + }; - let progressCalled = false; - Realm.open(config) - .progress((transferred, total) => { - progressCalled = true; - }) - .then(() => { - TestCase.assertTrue(progressCalled); - resolve(); - }) - .catch((e) => reject(e)); - - setTimeout(function() { - reject("Progress Notifications API failed to call progress callback for Realm constructor"); - }, 5000); - }); - }); - }); + return Promise.race([ + Realm.open(config).progress((transferred, total) => { progressCalled = true; }), + new Promise((_, reject) => setTimeout(() => reject("Progress Notifications API failed to call progress callback for Realm constructor"), 5000)) + ]); + }).then(() => TestCase.assertTrue(progressCalled)); }, testProgressNotificationsForRealmOpenAsync() { @@ -712,37 +688,36 @@ module.exports = { const realmName = uuid(); return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) - .then(() => { - return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { - return new Promise((resolve, reject) => { - let config = { - sync: { - user, - url: `realm://localhost:9080/~/${realmName}` - }, - schema: [{ name: 'Dog', properties: { name: 'string' } }], - }; + .then(() => Realm.Sync.User.login('http://localhost:9080', username, 'password')) + .then(user => { + return new Promise((resolve, reject) => { + let config = { + sync: { + user, + url: `realm://localhost:9080/~/${realmName}` + }, + schema: [{ name: 'Dog', properties: { name: 'string' } }], + }; - let progressCalled = false; + let progressCalled = false; - Realm.openAsync(config, - (error, realm) => { - if (error) { - reject(error); - return; - } + Realm.openAsync(config, + (error, realm) => { + if (error) { + reject(error); + return; + } - TestCase.assertTrue(progressCalled); - resolve(); - }, - (transferred, total) => { - progressCalled = true; - }); + TestCase.assertTrue(progressCalled); + resolve(); + }, + (transferred, total) => { + progressCalled = true; + }); - setTimeout(function() { - reject("Progress Notifications API failed to call progress callback for Realm constructor"); - }, 5000); - }); + setTimeout(function() { + reject("Progress Notifications API failed to call progress callback for Realm constructor"); + }, 5000); }); }); }, @@ -757,38 +732,37 @@ module.exports = { const realmName = uuid(); return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) - .then(() => { - return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { - let config = { - sync: { - user: user, - url: `realm://localhost:9080/~/${realmName}`, - partial: true, - error: (session, error) => console.log(error) - }, - schema: [{ name: 'Dog', properties: { name: 'string' } }] - }; + .then(() => Realm.Sync.User.login('http://localhost:9080', username, 'password')) + .then(user => { + let config = { + sync: { + user: user, + url: `realm://localhost:9080/~/${realmName}`, + partial: true, + error: (session, error) => console.log(error) + }, + schema: [{ name: 'Dog', properties: { name: 'string' } }] + }; - Realm.deleteFile(config); - const realm = new Realm(config); - TestCase.assertEqual(realm.objects('Dog').length, 0); - var results = realm.objects('Dog').filtered("name == 'Lassy 1'"); - var subscription = results.subscribe(); - TestCase.assertEqual(subscription.state, Realm.Sync.SubscriptionState.Creating); - return new Promise((resolve, reject) => { - subscription.addListener((subscription, state) => { - if (state == Realm.Sync.SubscriptionState.Complete) { - TestCase.assertEqual(results.length, 1); - TestCase.assertTrue(results[0].name === 'Lassy 1', "The object is not synced correctly"); - resolve(); - } - }); - setTimeout(function() { - reject("listener never called"); - }, 5000); + Realm.deleteFile(config); + const realm = new Realm(config); + TestCase.assertEqual(realm.objects('Dog').length, 0); + var results = realm.objects('Dog').filtered("name == 'Lassy 1'"); + var subscription = results.subscribe(); + TestCase.assertEqual(subscription.state, Realm.Sync.SubscriptionState.Creating); + return new Promise((resolve, reject) => { + subscription.addListener((subscription, state) => { + if (state == Realm.Sync.SubscriptionState.Complete) { + TestCase.assertEqual(results.length, 1); + TestCase.assertTrue(results[0].name === 'Lassy 1', "The object is not synced correctly"); + resolve(); + } }); - }) - }) + setTimeout(function() { + reject("listener never called"); + }, 5000); + }); + }); }, testPartialSyncAnonymous_ResultsListener() { @@ -801,38 +775,37 @@ module.exports = { const realmName = uuid(); return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) - .then(() => { - return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { - let config = { - sync: { - user: user, - url: `realm://localhost:9080/~/${realmName}`, - partial: true, - error: (session, error) => console.log(error) - }, - schema: [{ name: 'Dog', properties: { name: 'string' } }] - }; + .then(() => Realm.Sync.User.login('http://localhost:9080', username, 'password')) + .then(user => { + let config = { + sync: { + user: user, + url: `realm://localhost:9080/~/${realmName}`, + partial: true, + error: (session, error) => console.log(error) + }, + schema: [{ name: 'Dog', properties: { name: 'string' } }] + }; - Realm.deleteFile(config); - const realm = new Realm(config); - TestCase.assertEqual(realm.objects('Dog').length, 0); - var results = realm.objects('Dog').filtered("name == 'Lassy 1'"); - var subscription = results.subscribe(); - TestCase.assertEqual(subscription.state, Realm.Sync.SubscriptionState.Creating); - return new Promise((resolve, reject) => { - results.addListener((collection, changes) => { - if (subscription.state === Realm.Sync.SubscriptionState.Complete) { - TestCase.assertEqual(collection.length, 1); - TestCase.assertTrue(collection[0].name === 'Lassy 1', "The object is not synced correctly"); - resolve(); - } - }); - setTimeout(function() { - reject("listener never called"); - }, 5000); + Realm.deleteFile(config); + const realm = new Realm(config); + TestCase.assertEqual(realm.objects('Dog').length, 0); + var results = realm.objects('Dog').filtered("name == 'Lassy 1'"); + var subscription = results.subscribe(); + TestCase.assertEqual(subscription.state, Realm.Sync.SubscriptionState.Creating); + return new Promise((resolve, reject) => { + results.addListener((collection, changes) => { + if (subscription.state === Realm.Sync.SubscriptionState.Complete) { + TestCase.assertEqual(collection.length, 1); + TestCase.assertTrue(collection[0].name === 'Lassy 1', "The object is not synced correctly"); + resolve(); + } }); - }) - }) + setTimeout(function() { + reject("listener never called"); + }, 5000); + }); + }); }, testPartialSyncMultipleSubscriptions() { @@ -845,56 +818,55 @@ module.exports = { const realmName = uuid(); return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) - .then(() => { - return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { - let config = { - sync: { - user: user, - url: `realm://localhost:9080/~/${realmName}`, - partial: true, - error: (session, error) => console.log(error) - }, - schema: [{ name: 'Dog', properties: { name: 'string' } }] - }; + .then(() => Realm.Sync.User.login('http://localhost:9080', username, 'password')) + .then(user => { + let config = { + sync: { + user: user, + url: `realm://localhost:9080/~/${realmName}`, + partial: true, + error: (session, error) => console.log(error) + }, + schema: [{ name: 'Dog', properties: { name: 'string' } }] + }; - Realm.deleteFile(config); - const realm = new Realm(config); - TestCase.assertEqual(realm.objects('Dog').length, 0); - var results1 = realm.objects('Dog').filtered("name == 'Lassy 1'"); - var results2 = realm.objects('Dog').filtered("name == 'Lassy 2'"); - var subscription1 = results1.subscribe(); - var subscription2 = results2.subscribe(); + Realm.deleteFile(config); + const realm = new Realm(config); + TestCase.assertEqual(realm.objects('Dog').length, 0); + var results1 = realm.objects('Dog').filtered("name == 'Lassy 1'"); + var results2 = realm.objects('Dog').filtered("name == 'Lassy 2'"); + var subscription1 = results1.subscribe(); + var subscription2 = results2.subscribe(); - return new Promise((resolve, reject) => { - let called1 = false; - let called2 = false; - results1.addListener((collection, changeset) => { - if (subscription1.state == Realm.Sync.SubscriptionState.Complete) { - TestCase.assertEqual(collection.length, 1); - TestCase.assertTrue(collection[0].name === 'Lassy 1', "The object is not synced correctly"); - called1 = true; - if (called1 && called2) { - resolve(); - } + return new Promise((resolve, reject) => { + let called1 = false; + let called2 = false; + results1.addListener((collection, changeset) => { + if (subscription1.state == Realm.Sync.SubscriptionState.Complete) { + TestCase.assertEqual(collection.length, 1); + TestCase.assertTrue(collection[0].name === 'Lassy 1', "The object is not synced correctly"); + called1 = true; + if (called1 && called2) { + resolve(); } - }); - results2.addListener((collection, changeset) => { - if (subscription2.state == Realm.Sync.SubscriptionState.Complete) { - TestCase.assertEqual(collection.length, 1); - TestCase.assertTrue(collection[0].name === 'Lassy 2', "The object is not synced correctly"); - called2 = true; - if (called1 && called2) { - resolve(); - } - } - }); - - setTimeout(function() { - reject("listener never called"); - }, 5000); + } }); - }) - }) + results2.addListener((collection, changeset) => { + if (subscription2.state == Realm.Sync.SubscriptionState.Complete) { + TestCase.assertEqual(collection.length, 1); + TestCase.assertTrue(collection[0].name === 'Lassy 2', "The object is not synced correctly"); + called2 = true; + if (called1 && called2) { + resolve(); + } + } + }); + + setTimeout(function() { + reject("listener never called"); + }, 5000); + }); + }); }, testPartialSyncFailing() { @@ -907,23 +879,22 @@ module.exports = { const realmName = uuid(); return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) - .then(() => { - return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { - let config = { - sync: { - user: user, - url: `realm://localhost:9080/~/${realmName}`, - partial: false, // <---- calling subscribe should fail - error: (session, error) => console.log(error) - }, - schema: [{ name: 'Dog', properties: { name: 'string' } }] - }; + .then(() => Realm.Sync.User.login('http://localhost:9080', username, 'password')) + .then(user => { + let config = { + sync: { + user: user, + url: `realm://localhost:9080/~/${realmName}`, + partial: false, // <---- calling subscribe should fail + error: (session, error) => console.log(error) + }, + schema: [{ name: 'Dog', properties: { name: 'string' } }] + }; - Realm.deleteFile(config); - const realm = new Realm(config); - TestCase.assertEqual(realm.objects('Dog').length, 0); - TestCase.assertThrows(function () { var subscription = realm.objects('Dog').filtered("name == 'Lassy 1'").subscribe(); } ); - }); + Realm.deleteFile(config); + const realm = new Realm(config); + TestCase.assertEqual(realm.objects('Dog').length, 0); + TestCase.assertThrows(function () { var subscription = realm.objects('Dog').filtered("name == 'Lassy 1'").subscribe(); } ); }); }, @@ -937,36 +908,35 @@ module.exports = { const realmName = uuid(); return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) - .then(() => { - return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { - let config = { - sync: { - user: user, - url: `realm://localhost:9080/~/${realmName}`, - partial: true, - error: (session, error) => console.log(error) - }, - schema: [{ name: 'Dog', properties: { name: 'string' } }] - }; + .then(() => Realm.Sync.User.login('http://localhost:9080', username, 'password')) + .then(user => { + let config = { + sync: { + user: user, + url: `realm://localhost:9080/~/${realmName}`, + partial: true, + error: (session, error) => console.log(error) + }, + schema: [{ name: 'Dog', properties: { name: 'string' } }] + }; - Realm.deleteFile(config); - const realm = new Realm(config); - var results = realm.objects('Dog').filtered("name == 'Lassy 1'"); - var subscription = results.subscribe(); - TestCase.assertEqual(subscription.state, Realm.Sync.SubscriptionState.Creating); - return new Promise((resolve, reject) => { - results.addListener((collection, changes) => { - if (subscription.state === Realm.Sync.SubscriptionState.Complete) { - subscription.unsubscribe(); - } - if (subscription.state === Realm.Sync.SubscriptionState.Invalidated) { - resolve(); - } - }); - setTimeout(function() { - reject("listener never called"); - }, 5000); + Realm.deleteFile(config); + const realm = new Realm(config); + var results = realm.objects('Dog').filtered("name == 'Lassy 1'"); + var subscription = results.subscribe(); + TestCase.assertEqual(subscription.state, Realm.Sync.SubscriptionState.Creating); + return new Promise((resolve, reject) => { + results.addListener((collection, changes) => { + if (subscription.state === Realm.Sync.SubscriptionState.Complete) { + subscription.unsubscribe(); + } + if (subscription.state === Realm.Sync.SubscriptionState.Invalidated) { + resolve(); + } }); + setTimeout(function() { + reject("listener never called"); + }, 5000); }); }); }, From 5ff5df97be9e87e3c478de720aab7c8d97dc8022 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Fri, 2 Mar 2018 12:06:00 -0800 Subject: [PATCH 23/39] Fix running nested-list-helper.js with node 8 --- tests/js/nested-list-helper.js | 2 ++ tests/js/session-tests.js | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/js/nested-list-helper.js b/tests/js/nested-list-helper.js index e4b0803c..7de46e5e 100644 --- a/tests/js/nested-list-helper.js +++ b/tests/js/nested-list-helper.js @@ -9,6 +9,8 @@ const realmName = process.argv[4]; const realmModule = process.argv[5]; const Realm = require(realmModule); +// Ensure that schemas.js gets the correct module with `require('realm')` +require.cache[require.resolve('realm')] = require.cache[require.resolve(realmModule)]; let schemas = require(process.argv[2]); function createObjects(user) { diff --git a/tests/js/session-tests.js b/tests/js/session-tests.js index 1ef8a21c..ee01c3c6 100644 --- a/tests/js/session-tests.js +++ b/tests/js/session-tests.js @@ -69,8 +69,8 @@ function runOutOfProcess() { try { execFile(process.execPath, args, {cwd: tmpDir.name}, (error, stdout, stderr) => { if (error) { - console.error("runOutOfProcess failed\n", error); - reject(new Error(`Running ${modulePath} failed. error: ${error}`)); + console.error("runOutOfProcess failed\n", error, stdout, stderr); + reject(new Error(`Running ${args[0]} failed. error: ${error}`)); return; } From 384f43c6379e74a54cb45148f9c0e075adf6aeed Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Fri, 2 Mar 2018 12:13:59 -0800 Subject: [PATCH 24/39] Make testOfferPermissions more reliable The order of the permissions is undefined and will vary based on what order things happen to resolve on the server. --- tests/js/permission-tests.js | 39 +++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/tests/js/permission-tests.js b/tests/js/permission-tests.js index 8b849005..b92fb031 100644 --- a/tests/js/permission-tests.js +++ b/tests/js/permission-tests.js @@ -85,18 +85,31 @@ function waitForUpload(realm) { }); } +function permissionForPath(permissions, path) { + for (const permission of permissions) { + if (permission.path == path) { + return permission; + } + } +} + module.exports = { testApplyAndGetGrantedPermissions() { return createUsersWithTestRealms(1) .then(([user]) => { - return user.applyPermissions({ userId: `${user.identity}` }, `/${user.identity}/test`, 'read') + const path = `/${user.identity}/test`; + return user.applyPermissions({userId: `${user.identity}`}, `/${user.identity}/test`, 'read') .then(repeatUntil(() => user.getGrantedPermissions('any'), - permissions => permissions.length > 1)) + permissions => { + let permission = permissionForPath(permissions, path); + return permission && !permission.mayWrite; + })) .then(permissions => { - TestCase.assertEqual(permissions[0].path, `/${user.identity}/test`); - TestCase.assertEqual(permissions[0].mayRead, true); - TestCase.assertEqual(permissions[0].mayWrite, false); - TestCase.assertEqual(permissions[0].mayManage, false); + let permission = permissionForPath(permissions, path); + TestCase.assertDefined(permission); + TestCase.assertEqual(permission.mayRead, true); + TestCase.assertEqual(permission.mayWrite, false); + TestCase.assertEqual(permission.mayManage, false); }); }); }, @@ -104,19 +117,21 @@ module.exports = { testOfferPermissions() { return createUsersWithTestRealms(2) .then(([user1, user2]) => { + const path = `/${user1.identity}/test`; return user1.offerPermissions(`/${user1.identity}/test`, 'read') .then(token => user2.acceptPermissionOffer(token)) .then(realmUrl => { - TestCase.assertEqual(realmUrl, `/${user1.identity}/test`); + TestCase.assertEqual(realmUrl, path); return realmUrl; }) .then(repeatUntil(() => user2.getGrantedPermissions('any'), - permissions => permissions.length > 1)) + permissions => permissions.length > 2 && permissionForPath(permissions, path))) .then(permissions => { - TestCase.assertEqual(permissions[2].path, `/${user1.identity}/test`); - TestCase.assertEqual(permissions[2].mayRead, true); - TestCase.assertEqual(permissions[2].mayWrite, false); - TestCase.assertEqual(permissions[2].mayManage, false); + let permission = permissionForPath(permissions, path) + TestCase.assertDefined(permission); + TestCase.assertEqual(permission.mayRead, true); + TestCase.assertEqual(permission.mayWrite, false); + TestCase.assertEqual(permission.mayManage, false); }); }); }, From fadd35bb262c0632475cf00a4ac57970fa119714 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Fri, 2 Mar 2018 15:08:06 -0800 Subject: [PATCH 25/39] Add a typescript declaration for Sync.SubscriptionState --- lib/index.d.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/index.d.ts b/lib/index.d.ts index 490b43fa..3358b83d 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -406,7 +406,7 @@ declare namespace Realm.Sync { * @see { @link https://realm.io/docs/javascript/latest/api/Realm.Sync.Subscription.html } */ class Subscription { - readonly state: number; + readonly state: SubscriptionState; readonly error: string; unsubscribe(): void; @@ -414,6 +414,14 @@ declare namespace Realm.Sync { removeListener(subscruptionCallback: SubscriptionNotificationCallback): void; } + enum SubscriptionState { + Error, + Creating, + Pending, + Complete, + Invalidated, + } + /** * AuthError * @see { @link https://realm.io/docs/javascript/latest/api/Realm.Sync.AuthError.html } From d1a4e899d87e4bf900fbdb29fc45c8713fa0b132 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Fri, 2 Mar 2018 15:21:43 -0800 Subject: [PATCH 26/39] Fix the Permissions typescript definitions --- lib/index.d.ts | 69 +++++++++++++++++++++--------------- tests/js/permission-tests.js | 2 +- 2 files changed, 42 insertions(+), 29 deletions(-) diff --git a/lib/index.d.ts b/lib/index.d.ts index 3358b83d..21f0e93a 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -81,7 +81,7 @@ declare namespace Realm { path?: string; readOnly?: boolean; inMemory?: boolean; - schema?: ObjectClass[] | ObjectSchema[]; + schema?: (ObjectClass | ObjectSchema)[]; schemaVersion?: number; sync?: Realm.Sync.SyncConfiguration; deleteRealmIfMigrationNeeded?: boolean; @@ -483,6 +483,42 @@ declare namespace Realm.Sync { } } +declare namespace Realm.Permissions { + class Permission { + static schema: ObjectSchema; + + identity: string; + canRead: boolean; + canUpdate: boolean; + canDelete: boolean; + canSetPermissions: boolean; + canQuery: boolean; + canCreate: boolean; + canModifySchema: boolean; + } + + class User { + static schema: ObjectSchema; + identity: string; + } + + class Role { + static schema: ObjectSchema; + name: string; + members: User[]; + } + + class Class { + static schema: ObjectSchema; + class_name: string; + permissions: Permission[]; + } + + class Realm { + static schema: ObjectSchema; + permissions: Permission[]; + } +} interface ProgressPromise extends Promise { progress(callback: Realm.Sync.ProgressNotificationCallback): Promise @@ -552,7 +588,7 @@ declare class Realm { * @param {boolean} update? * @returns T */ - create(type: string | Realm.ObjectClass | Function, properties: T & Realm.ObjectPropsType, update?: boolean): T; + create(type: string | Realm.ObjectClass | Function, properties: T | Realm.ObjectPropsType, update?: boolean): T; /** * @param {Realm.Object|Realm.Object[]|Realm.List|Realm.Results|any} object @@ -628,33 +664,10 @@ declare class Realm { * @returns boolean */ compact(): boolean; -} -declare namespace Realm.Permissions { - interface Permission { - identity: string; - canRead: boolean; - canUpdate: boolean; - canDelete: boolean; - canSetPermissions: boolean; - canQuery: boolean; - canCreate: boolean; - canModifySchema: boolean; - } - interface User { - identity: string; - } - interface Role { - name: string; - members: User[]; - } - interface Class { - class_name: string; - permissions: Permission[]; - } - interface Realm { - permissions: Permission[]; - } + privileges() : Realm.Permissions.Realm; + privileges(objectType: string | Realm.ObjectSchema | Function) : Realm.Permissions.Class; + privileges(obj: Realm.Object) : Realm.Permissions.Class; } declare module 'realm' { diff --git a/tests/js/permission-tests.js b/tests/js/permission-tests.js index b92fb031..8378dfbc 100644 --- a/tests/js/permission-tests.js +++ b/tests/js/permission-tests.js @@ -147,7 +147,7 @@ module.exports = { .then(t => { token = t; return user1.invalidatePermissionOffer(token); }) // Since we don't yet support notification when the invalidation has gone through, // wait for a bit and hope the server is done processing. - .then(wait(100)) + .then(() => wait(100)) .then(() => user2.acceptPermissionOffer(token)) // We want the call to fail, i.e. the catch() below should be called. .then(() => { throw new Error("User was able to accept an invalid permission offer token"); }) From 1f2173b5999297e082da518e5d3631b3db21fc9b Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Sat, 3 Mar 2018 12:55:11 +0100 Subject: [PATCH 27/39] Avoid Table::clear() when using partial sync (#1680) * Avoid Table::clear() when using partial sync * Updated to sync 3.0.0-beta.10 * Update object store --- CHANGELOG.md | 4 ++-- src/js_realm.hpp | 8 +++++++- src/object-store | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8c427cf..384a78f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,8 +18,8 @@ * [Sync] Added class `Realm.Sync.Subscription` to support partial synced Realms. ### Internal -* Updated to Realm Core 5.8.0. -* Updated to Realm Sync 3.0.0-beta.9. +* Updated to Realm Core 5.3.0. +* Updated to Realm Sync 3.0.0-beta.10. * Tested against Realm Object Server 3.0.0-alpha.8. diff --git a/src/js_realm.hpp b/src/js_realm.hpp index 961032a7..2d0a8d57 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -898,7 +898,13 @@ void RealmClass::delete_all(ContextType ctx, ObjectType this_object, Argument } for (auto objectSchema : realm->schema()) { - ObjectStore::table_for_object_type(realm->read_group(), objectSchema.name)->clear(); + auto table = ObjectStore::table_for_object_type(realm->read_group(), objectSchema.name); + if (realm->is_partial()) { + realm::Results(realm, *table).clear(); + } + else { + table->clear(); + } } } diff --git a/src/object-store b/src/object-store index bb559df9..b250563e 160000 --- a/src/object-store +++ b/src/object-store @@ -1 +1 @@ -Subproject commit bb559df9237ece49f9c889993f7c1aff619b48f9 +Subproject commit b250563ea1eb9f32ec7dbd76f2c6f8f1a26914cc From 4e4c16946f4d8fcf1a068cf590b5c8fcb3dbe745 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Sat, 3 Mar 2018 12:57:49 +0100 Subject: [PATCH 28/39] [2.3.0-beta.1] Bump version --- CHANGELOG.md | 2 +- dependencies.list | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 384a78f9..211d1c50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -2.3.0 Release notes (2018-3-1) +2.3.0 Release notes (2018-3-3) ============================================================= ### Breaking changes * [Sync] Sync protocol changed to version 24. diff --git a/dependencies.list b/dependencies.list index a87aea2f..1bff81a7 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js -VERSION=2.3.0-alpha.15 +VERSION=2.3.0-beta.1 REALM_CORE_VERSION=5.3.0 REALM_SYNC_VERSION=3.0.0-beta.10 REALM_OBJECT_SERVER_VERSION=3.0.0-alpha.8 diff --git a/package.json b/package.json index eedaa630..3dbfadaa 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": "2.3.0-alpha.15", + "version": "2.3.0-beta.1", "license": "Apache-2.0", "homepage": "https://realm.io", "keywords": [ From f9b6736bcbec27a949ecacdf75caeee2c01674ae Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Mon, 5 Mar 2018 10:46:48 +0100 Subject: [PATCH 29/39] Updating to Realm Sync 2.2.15 (#1684) --- CHANGELOG.md | 16 ++++++++++++++++ dependencies.list | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22e33f53..9d35b134 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +X.Y.Z Release notes +============================================================= +### Breaking changes +* None. + +### Enhancements +* None. + +### Bug fixes +* [Object Server] Fixed race condition in handling of session bootstrapping in client. + + +### Internal +* Updated to Realm Sync 2.2.15. + + 2.2.13 Release notes (2018-3-2) ============================================================= ### Breaking changes diff --git a/dependencies.list b/dependencies.list index be864702..d3a85977 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js VERSION=2.2.13 REALM_CORE_VERSION=5.1.2 -REALM_SYNC_VERSION=2.2.14 +REALM_SYNC_VERSION=2.2.15 REALM_OBJECT_SERVER_VERSION=2.5.1 From 4660c5ff64458e3b76150e551da04f5c30ac78d5 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Mon, 5 Mar 2018 10:48:44 +0100 Subject: [PATCH 30/39] [2.2.14] Bump version --- CHANGELOG.md | 2 +- dependencies.list | 2 +- package.json | 2 +- src/RealmJS.xcodeproj/project.pbxproj | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d35b134..010b1024 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -X.Y.Z Release notes +2.2.14 Release notes (2018-3-5) ============================================================= ### Breaking changes * None. diff --git a/dependencies.list b/dependencies.list index d3a85977..2e946f06 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js -VERSION=2.2.13 +VERSION=2.2.14 REALM_CORE_VERSION=5.1.2 REALM_SYNC_VERSION=2.2.15 REALM_OBJECT_SERVER_VERSION=2.5.1 diff --git a/package.json b/package.json index 09c5295f..22312c5f 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": "2.2.13", + "version": "2.2.14", "license": "Apache-2.0", "homepage": "https://realm.io", "keywords": [ diff --git a/src/RealmJS.xcodeproj/project.pbxproj b/src/RealmJS.xcodeproj/project.pbxproj index b972afbf..32b451ac 100644 --- a/src/RealmJS.xcodeproj/project.pbxproj +++ b/src/RealmJS.xcodeproj/project.pbxproj @@ -961,7 +961,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2.2.13; + CURRENT_PROJECT_VERSION = 2.2.14; CXX = "$(SRCROOT)/../scripts/ccache-clang++.sh"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -1025,7 +1025,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2.2.13; + CURRENT_PROJECT_VERSION = 2.2.14; CXX = "$(SRCROOT)/../scripts/ccache-clang++.sh"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; From 9da59d8f04734a3f1cf63242b89e5eee0ccb15ed Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Mon, 5 Mar 2018 09:46:24 -0800 Subject: [PATCH 31/39] Reformat permissions-tests.js to a consistent 4-space indent --- tests/js/permission-tests.js | 305 ++++++++++++++++++----------------- 1 file changed, 157 insertions(+), 148 deletions(-) diff --git a/tests/js/permission-tests.js b/tests/js/permission-tests.js index 8378dfbc..ccfe3f2b 100644 --- a/tests/js/permission-tests.js +++ b/tests/js/permission-tests.js @@ -16,33 +16,33 @@ // //////////////////////////////////////////////////////////////////////////// - 'use strict'; var Realm = require('realm'); var TestCase = require('./asserts'); function uuid() { - return '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); - }); + return '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); + }); } function createUsersWithTestRealms(count) { - const createUserWithTestRealm = () => { - return Realm.Sync.User.register('http://localhost:9080', uuid(), 'password') - .then(user => { - new Realm({sync: {user, url: 'realm://localhost:9080/~/test'}}).close(); - return user; - }); - }; + const createUserWithTestRealm = () => { + return Realm.Sync.User + .register('http://localhost:9080', uuid(), 'password') + .then(user => { + new Realm({sync: {user, url: 'realm://localhost:9080/~/test'}}).close(); + return user; + }); + }; - return Promise.all(Array.from({length: count}, createUserWithTestRealm)); + return Promise.all(Array.from({length: count}, createUserWithTestRealm)); } function wait(t) { - return new Promise(resolve => setTimeout(resolve, t)); + return new Promise(resolve => setTimeout(resolve, t)); } function repeatUntil(fn, predicate) { @@ -58,18 +58,18 @@ function repeatUntil(fn, predicate) { } function subscribe(results) { - const subscription = results.subscribe() - return new Promise((resolve, reject) => { - subscription.addListener((subscription, state) => { - if (state == Realm.Sync.SubscriptionState.Complete) { - resolve(); - } - else if (state == Realm.Sync.SubscriptionState.Error) { - reject(); - } - }); - setTimeout(() => reject("listener never called"), 5000); - }); + const subscription = results.subscribe(); + return new Promise((resolve, reject) => { + subscription.addListener((subscription, state) => { + if (state == Realm.Sync.SubscriptionState.Complete) { + resolve(); + } + else if (state == Realm.Sync.SubscriptionState.Error) { + reject(); + } + }); + setTimeout(() => reject("listener never called"), 5000); + }); } function waitForUpload(realm) { @@ -80,151 +80,160 @@ function waitForUpload(realm) { session.removeProgressNotification(callback); resolve(realm); } - } + }; session.addProgressNotification('upload', 'forCurrentlyOutstandingWork', callback); }); } function permissionForPath(permissions, path) { - for (const permission of permissions) { - if (permission.path == path) { - return permission; + for (const permission of permissions) { + if (permission.path == path) { + return permission; + } } - } } module.exports = { testApplyAndGetGrantedPermissions() { - return createUsersWithTestRealms(1) - .then(([user]) => { - const path = `/${user.identity}/test`; - return user.applyPermissions({userId: `${user.identity}`}, `/${user.identity}/test`, 'read') - .then(repeatUntil(() => user.getGrantedPermissions('any'), - permissions => { - let permission = permissionForPath(permissions, path); - return permission && !permission.mayWrite; - })) - .then(permissions => { - let permission = permissionForPath(permissions, path); - TestCase.assertDefined(permission); - TestCase.assertEqual(permission.mayRead, true); - TestCase.assertEqual(permission.mayWrite, false); - TestCase.assertEqual(permission.mayManage, false); - }); + return createUsersWithTestRealms(1).then(([user]) => { + const path = `/${user.identity}/test`; + return user + .applyPermissions({userId: `${user.identity}`}, + `/${user.identity}/test`, 'read') + .then(repeatUntil(() => user.getGrantedPermissions('any'), + permissions => { + let permission = permissionForPath(permissions, path); + return permission && !permission.mayWrite; + })) + .then(permissions => { + let permission = permissionForPath(permissions, path); + TestCase.assertDefined(permission); + TestCase.assertEqual(permission.mayRead, true); + TestCase.assertEqual(permission.mayWrite, false); + TestCase.assertEqual(permission.mayManage, false); + }); }); }, testOfferPermissions() { - return createUsersWithTestRealms(2) - .then(([user1, user2]) => { - const path = `/${user1.identity}/test`; - return user1.offerPermissions(`/${user1.identity}/test`, 'read') - .then(token => user2.acceptPermissionOffer(token)) - .then(realmUrl => { - TestCase.assertEqual(realmUrl, path); - return realmUrl; - }) - .then(repeatUntil(() => user2.getGrantedPermissions('any'), - permissions => permissions.length > 2 && permissionForPath(permissions, path))) - .then(permissions => { - let permission = permissionForPath(permissions, path) - TestCase.assertDefined(permission); - TestCase.assertEqual(permission.mayRead, true); - TestCase.assertEqual(permission.mayWrite, false); - TestCase.assertEqual(permission.mayManage, false); - }); + return createUsersWithTestRealms(2).then(([user1, user2]) => { + const path = `/${user1.identity}/test`; + return user1.offerPermissions(`/${user1.identity}/test`, 'read') + .then(token => user2.acceptPermissionOffer(token)) + .then(realmUrl => { + TestCase.assertEqual(realmUrl, path); + return realmUrl; + }) + .then(repeatUntil(() => user2.getGrantedPermissions('any'), + permissions => permissions.length > 2 + && permissionForPath(permissions, path))) + .then(permissions => { + let permission = permissionForPath(permissions, path) + TestCase.assertDefined(permission); + TestCase.assertEqual(permission.mayRead, true); + TestCase.assertEqual(permission.mayWrite, false); + TestCase.assertEqual(permission.mayManage, false); + }); }); }, testInvalidatePermissionOffer() { - let user1, user2, token; - return createUsersWithTestRealms(2) - .then(users => { - user1 = users[0]; - user2 = users[1]; - return user1.offerPermissions(`/${user1.identity}/test`, 'read'); - }) - .then(t => { token = t; return user1.invalidatePermissionOffer(token); }) - // Since we don't yet support notification when the invalidation has gone through, - // wait for a bit and hope the server is done processing. - .then(() => wait(100)) - .then(() => user2.acceptPermissionOffer(token)) - // We want the call to fail, i.e. the catch() below should be called. - .then(() => { throw new Error("User was able to accept an invalid permission offer token"); }) - .catch(error => { - try { - TestCase.assertEqual(error.message, 'The permission offer is expired.'); - TestCase.assertEqual(error.statusCode, 701); - } - catch (e) { - throw new Error(e); - } - }); + let user1, user2, token; + return createUsersWithTestRealms(2) + .then(users => { + user1 = users[0]; + user2 = users[1]; + return user1.offerPermissions(`/${user1.identity}/test`, 'read'); + }) + .then(t => { + token = t; + return user1.invalidatePermissionOffer(token); + }) + // Since we don't yet support notification when the invalidation has + // gone through, wait for a bit and hope the server is done + // processing. + .then(() => wait(100)) + .then(() => user2.acceptPermissionOffer(token)) + // We want the call to fail, i.e. the catch() below should be + // called. + .then(() => { + throw new Error("User was able to accept an invalid permission offer token"); + }) + .catch(error => { + try { + TestCase.assertEqual(error.message, 'The permission offer is expired.'); + TestCase.assertEqual(error.statusCode, 701); + } + catch (e) { + throw new Error(e); + } + }); }, testObjectPermissions() { - let config = (user, url) => { - return { - schema: [ - Realm.Permissions.Permission, - Realm.Permissions.User, - Realm.Permissions.Role, - { - name: 'Object', - properties: { - value: 'int', - permissions: '__Permission[]' - } - } - ], - sync: {user, url, partial: true} + let config = (user, url) => { + return { + schema: [ + Realm.Permissions.Permission, + Realm.Permissions.User, + Realm.Permissions.Role, + { + name: 'Object', + properties: { + value: 'int', + permissions: '__Permission[]' + } + } + ], + sync: {user, url, partial: true} + }; }; - }; - let owner, otherUser - return Realm.Sync.User.register('http://localhost:9080', uuid(), 'password') - .then(user => { - owner = user; - new Realm({sync: {user, url: 'realm://localhost:9080/~/test'}}).close(); - return Realm.Sync.User.register('http://localhost:9080', uuid(), 'password') - }) - .then(user => { - otherUser = user; - return owner.applyPermissions({userId: otherUser.identity}, `/${owner.identity}/test`, 'read') - }) - .then(() => { - let realm = new Realm(config(owner, 'realm://localhost:9080/~/test')); - realm.write(() => { - let user = realm.create(Realm.Permissions.User, {id: otherUser.identity}) - let role = realm.create(Realm.Permissions.Role, {name: 'reader'}) - role.members.push(user) + let owner, otherUser + return Realm.Sync.User + .register('http://localhost:9080', uuid(), 'password') + .then(user => { + owner = user; + new Realm({sync: {user, url: 'realm://localhost:9080/~/test'}}).close(); + return Realm.Sync.User.register('http://localhost:9080', uuid(), 'password') + }) + .then(user => { + otherUser = user; + return owner.applyPermissions({userId: otherUser.identity}, + `/${owner.identity}/test`, 'read') + }) + .then(() => { + let realm = new Realm(config(owner, 'realm://localhost:9080/~/test')); + realm.write(() => { + let user = realm.create(Realm.Permissions.User, {id: otherUser.identity}) + let role = realm.create(Realm.Permissions.Role, {name: 'reader'}) + role.members.push(user) - let obj1 = realm.create('Object', {value: 1}) - let obj2 = realm.create('Object', {value: 2}) - obj2.permissions.push(realm.create(Realm.Permissions.Permission, - {role: role, canRead: true, canUpdate: false})) - }); - return waitForUpload(realm).then(() => realm.close()); - }) - .then(() => Realm.open(config(otherUser, `realm://localhost:9080/${owner.identity}/test`))) - .then((realm) => subscribe(realm.objects('Object')).then(() => realm)) - .then((realm) => { - // Should have full access to the Realm as a whole - TestCase.assertSimilar('object', realm.privileges(), - {read: true, update: true, modifySchema: true, setPermissions: true}); - TestCase.assertSimilar('object', realm.privileges('Object'), - {read: true, update: true, create: true, subscribe: true, setPermissions: true}); - // Verify that checking via constructor works too - TestCase.assertSimilar('object', realm.privileges(Realm.Permissions.User), - {read: true, update: true, create: true, subscribe: true, setPermissions: true}); + let obj1 = realm.create('Object', {value: 1}); + let obj2 = realm.create('Object', {value: 2}); + obj2.permissions.push(realm.create(Realm.Permissions.Permission, + {role: role, canRead: true, canUpdate: false})) + }); + return waitForUpload(realm).then(() => realm.close()); + }) + .then(() => Realm.open(config(otherUser, `realm://localhost:9080/${owner.identity}/test`))) + .then((realm) => subscribe(realm.objects('Object')).then(() => realm)) + .then((realm) => { + // Should have full access to the Realm as a whole + TestCase.assertSimilar('object', realm.privileges(), + {read: true, update: true, modifySchema: true, setPermissions: true}); + TestCase.assertSimilar('object', realm.privileges('Object'), + {read: true, update: true, create: true, subscribe: true, setPermissions: true}); + // Verify that checking via constructor works too + TestCase.assertSimilar('object', realm.privileges(Realm.Permissions.User), + {read: true, update: true, create: true, subscribe: true, setPermissions: true}); - // Should only be able to see the second object - let results = realm.objects('Object') - TestCase.assertEqual(results.length, 1); - TestCase.assertEqual(results[0].value, 2); - TestCase.assertSimilar('object', realm.privileges(results[0]), - {read: true, update: false, delete: false, setPermissions: false}); - realm.close(); - }); + // Should only be able to see the second object + let results = realm.objects('Object') + TestCase.assertEqual(results.length, 1); + TestCase.assertEqual(results[0].value, 2); + TestCase.assertSimilar('object', realm.privileges(results[0]), + {read: true, update: false, delete: false, setPermissions: false}); + realm.close(); + }); } } - From 3433654a97a5c38f345391b0e362999ec9008919 Mon Sep 17 00:00:00 2001 From: astigsen Date: Tue, 6 Mar 2018 13:43:10 +0100 Subject: [PATCH 32/39] Removed backlink queries (for now) --- docs/tutorials/query-language.md | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/docs/tutorials/query-language.md b/docs/tutorials/query-language.md index 3677ede0..57cb0b9a 100644 --- a/docs/tutorials/query-language.md +++ b/docs/tutorials/query-language.md @@ -95,16 +95,3 @@ Example: // Find contacts with friends above 21 in SF let teens = realm.objects('Contact').filtered('SUBQUERY(friends, $friend, $friend.age > 21 AND $friend.city = "SF").@count > 0'); ``` - -### Backlink queries - -Other objects can link to an object and you can query on that releationship using the `@links` and `@links.ClassName.PropertyName` syntax: - -Example: -```JS -// Find contacts with no incomming links -let lonely = realm.objects('Contact').filtered('@links.@count == 0'); - -// Find contacts where someone from SF has them as friends -realm.objects('Contact').filtered('@links.Contact.friends.city == "SF"'); -``` From 83715e840fbde675b5d25a46f3bacf9a993418a8 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Tue, 6 Mar 2018 18:05:17 +0100 Subject: [PATCH 33/39] Adding API doc and changelog. --- CHANGELOG.md | 9 ++- docs/permission.js | 162 ++++++++++++++++++++++++++++++++++++++++++++- docs/realm.js | 21 ++++++ 3 files changed, 189 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8c427cf..2fc2cd79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,14 @@ - Any number of sort/distinct conditions can be indicated, they will be applied in the specified order. - Sort or distinct cannot operate independently, these conditions must be attached to at least one query filter. * [Sync] Added `Realm.Results.subscribe()` to subscribe to partial synced Realms. -* [Sync] Added class `Realm.Sync.Subscription` to support partial synced Realms. +* [Sync] Added class `Realm.Sync.Subscription` and enum `Realm.Sync.SubscriptionState` to support partial synced Realms. +* [Sync] Added an object-level permission subsystem. It is possible to grant fine-grained priviliges to users. +* Added object-level permissions: + - Schemas `Realm.Permissions.Realm`, `Realm.Permissions.Class`, `Realm.Permissions.Role`, `Realm.Permissions.User`, and `Realm.Permissions.Permission` to support working with permissions. These schemas can be used in user-defined Realms and schemas. + - Permissions are enforced by the object server but connectivity is not required. + - Method `Realm.privilges()` to compute privileges on a Realm, a Realm object schema, or a Realm object. The method returns either a `Realm.Permissions.Realm` or `Realm.Permissions.Class` object. + - For non-synced Realms, all privileges are always granted. + - For more details, please read the reference documentation. ### Internal * Updated to Realm Core 5.8.0. diff --git a/docs/permission.js b/docs/permission.js index 450ae7f9..dc4d03fb 100644 --- a/docs/permission.js +++ b/docs/permission.js @@ -21,7 +21,7 @@ * They are created exclusively by the client and are processed by the server * as indicated by the status fields. * PermissionChange objects allow to grant and revoke permissions by setting - * mayRead, mayWrite and mayManage accordingly. + * mayRead, mayWrite and mayManage accordingly. * If any of these flags are not set, these are merged * with either the existing or default permissions as applicable. As a * side-effect this causes that the default permissions are permanently @@ -30,7 +30,7 @@ * ErrorCode will be updated accordingly. */ class PermissionChange { - + /** * Gets the unique identifier of this object in the Management realm. * @type {string} @@ -229,3 +229,161 @@ class PermissionOfferResponse { */ get realmUrl() {} } + + + +/** + * A permission which can be applied to a Realm, Class, or specific Object. + * Permissions are applied by adding the permission to the Realm.Permission singleton + * object, the RealmClass.Permission object for the desired class, or to a user-defined + * Realm.List property on a specific Object instance. The meaning of each of + * the properties of Permission depend on what the permission is applied to, and so are + * left undocumented here. + * @since 2.3.0 +*/ +class Permission { + + /** + * The Role which this Permission applies to. All users within the Role are + * granted the permissions specified by the fields below any + * objects/classes/realms which use this Permission. + * + * This property cannot be modified once set. + * @type {Role} + */ + get role() {} + + /** + * Whether the user can read the object to which this Permission is attached. + * @type {boolean} + */ + get canRead() {} + + /** + * Whether the user can modify the object to which this Permission is attached. + * @type {boolean} + */ + get canUpdate() {} + + /** + * Whether the user can delete the object to which this Permission is attached. + * + * This property is only applicable to Permissions attached to Objects, and not + * to Realms or Classes. + * @type {boolean} + */ + get canDelete() {} + + /** + * Whether the user can add or modify Permissions for the object which this + * Permission is attached to. + * @type {boolean} + */ + get canSetPermissions() {} + + /** + * Whether the user can subscribe to queries for this object type. + * + * This property is only applicable to Permissions attached to Classes, and not + * to Realms or Objects. + * @type {boolean} + */ + get canQuery() {} + + /** + * Whether the user can create new objects of the type this Permission is attached to. + * + * This property is only applicable to Permissions attached to Classes, and not + * to Realms or Objects. + * @type {boolean} + */ + get canCreate() {} + + /** + * Whether the user can modify the schema of the Realm which this + * Permission is attached to. + * + * This property is only applicable to Permissions attached to Realms, and not + * to Realms or Objects. + * @type {boolean} + */ + get canModifySchema() {} +} + +/** + * A representation of a sync user within the permissions system. + * + * User objects are created automatically for each sync user which connects to + * a Realm, and can also be created manually if you wish to grant permissions to a user + * which has not yet connected to this Realm. + * @since 2.3.0 + */ +class User { + /** + * The unique Realm Object Server user ID string identifying this user. This will have + * the same value as Realm.Sync.User.identity. + * @type {string} + */ + get id() {} +} + +/** + * A Role within the permissions system. + * + * A Role consists of a name for the role and a list of users which are members of the role. + * Roles are granted privileges on Realms, Classes and Objects, and in turn grant those + * privileges to all users which are members of the role. + * A role named "everyone" is automatically created in new Realms, and all new users which + * connect to the Realm are automatically added to it. Any other roles you wish to use are + * managed as normal Realm objects. + * @since 2.3.0 + */ +class Role { + /** + * The name of the Role. + * @type {string} + */ + get name() {} + + /** + * The users which belong to the role. + * @type {Array} + */ + get members() {} +} + +/** + * An object which describes class-wide permissions. + * + * An instance of this object is automatically created in the Realm for class in your schema, + * and should not be created manually. + * @since 2.3.0 + */ +class Class { + /** + * The name of the class which these permissions apply to. + * @type {string} + */ + get class_name() {} + + /** + * The permissions for this class. + * @type {Array} + */ + get permissions() {} +} + +/** + * A singleton object which describes Realm-wide permissions. + * + * An object of this type is automatically created in the Realm for you, and more objects + * cannot be created manually. + * @since 2.3.0 + */ +class Realm { + /** + * The permissions for the Realm. + * @type {Array} + */ + get permissions() {} +} diff --git a/docs/realm.js b/docs/realm.js index a04c259d..359a714a 100644 --- a/docs/realm.js +++ b/docs/realm.js @@ -125,6 +125,27 @@ class Realm { */ close() {} + /** + * Returns the granted privilges. + * + * This combines all privileges granted on the Realm/Class/Object by all Roles which + * the current User is a member of into the final privileges which will + * be enforced by the server. + * + * The privilege calculation is done locally using cached data, and inherently may + * be stale. It is possible that this method may indicate that an operation is + * permitted but the server will still reject it if permission is revoked before + * the changes have been integrated on the server. + * + * Non-synchronized Realms always have permission to perform all operations. + * + * @param {(Realm~ObjectType|Realm.Object)} arg - the object type or the object to compute priviliges from + * @returns {Object} as the computed priviliges as properties + * @since 2.3.0 + * @see {Realm.Permissions} for details of priviliges and roles. + */ + privileges(arg) {} + /** * Create a new Realm object of the given type and with the specified properties. * @param {Realm~ObjectType} type - The type of Realm object to create. From 928db78339aa97ba12fa64984250e122d0da63a9 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Wed, 7 Mar 2018 10:04:15 +0100 Subject: [PATCH 34/39] [2.3.0-beta.2] Bump version --- CHANGELOG.md | 2 +- dependencies.list | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 120afc60..da96615f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -2.3.0 Release notes (2018-3-3) +2.3.0 Release notes (2018-3-7) ============================================================= ### Breaking changes * [Sync] Sync protocol changed to version 24. diff --git a/dependencies.list b/dependencies.list index 1bff81a7..9dca6ef3 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js -VERSION=2.3.0-beta.1 +VERSION=2.3.0-beta.2 REALM_CORE_VERSION=5.3.0 REALM_SYNC_VERSION=3.0.0-beta.10 REALM_OBJECT_SERVER_VERSION=3.0.0-alpha.8 diff --git a/package.json b/package.json index 154dc4dc..842cb48b 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": "2.3.0-beta.1", + "version": "2.3.0-beta.2", "license": "Apache-2.0", "homepage": "https://realm.io", "keywords": [ From d846794cd535deddda8266c19f2b68767b5155f9 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Thu, 8 Mar 2018 15:44:11 +0100 Subject: [PATCH 35/39] Deprecated setFeatureToken(). --- CHANGELOG.md | 1 + lib/extensions.js | 6 +----- lib/index.d.ts | 4 ++++ 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da96615f..34b157a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ - Method `Realm.privilges()` to compute privileges on a Realm, a Realm object schema, or a Realm object. The method returns either a `Realm.Permissions.Realm` or `Realm.Permissions.Class` object. - For non-synced Realms, all privileges are always granted. - For more details, please read the reference documentation. +* [Sync] Decrepated `Realm.Sync.setFeatureToken` (#1689). ### Internal * Updated to Realm Core 5.3.0. diff --git a/lib/extensions.js b/lib/extensions.js index 033d305c..2f3792f4 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -151,11 +151,7 @@ module.exports = function(realmConstructor) { if (realmConstructor.Sync._setFeatureToken) { realmConstructor.Sync.setFeatureToken = function(featureToken) { - if (typeof featureToken !== 'string' && !(featureToken instanceof String)) { - throw new Error("featureToken should be a string"); - } - - realmConstructor.Sync._setFeatureToken(featureToken.trim()); + console.log('Realm.Sync.setFeatureToken() is deprecated and you can remove any calls to it.'); } } diff --git a/lib/index.d.ts b/lib/index.d.ts index 21f0e93a..f7fa3cdd 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -448,6 +448,10 @@ declare namespace Realm.Sync { function removeListener(regex: string, name: string, changeCallback: (changeEvent: ChangeEvent) => void): Promise; function setLogLevel(logLevel: 'all' | 'trace' | 'debug' | 'detail' | 'info' | 'warn' | 'error' | 'fatal' | 'off'): void; function initiateClientReset(path: string): void; + + /** + * @deprecated, to be removed in future versions + */ function setFeatureToken(token: string): void; type Instruction = { From 1066722a09d8f34d90e7ae810d851486124c76d2 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Thu, 8 Mar 2018 15:47:50 +0100 Subject: [PATCH 36/39] Revert "Deprecated setFeatureToken()." This reverts commit d846794cd535deddda8266c19f2b68767b5155f9. --- CHANGELOG.md | 1 - lib/extensions.js | 6 +++++- lib/index.d.ts | 4 ---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34b157a7..da96615f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,6 @@ - Method `Realm.privilges()` to compute privileges on a Realm, a Realm object schema, or a Realm object. The method returns either a `Realm.Permissions.Realm` or `Realm.Permissions.Class` object. - For non-synced Realms, all privileges are always granted. - For more details, please read the reference documentation. -* [Sync] Decrepated `Realm.Sync.setFeatureToken` (#1689). ### Internal * Updated to Realm Core 5.3.0. diff --git a/lib/extensions.js b/lib/extensions.js index 2f3792f4..033d305c 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -151,7 +151,11 @@ module.exports = function(realmConstructor) { if (realmConstructor.Sync._setFeatureToken) { realmConstructor.Sync.setFeatureToken = function(featureToken) { - console.log('Realm.Sync.setFeatureToken() is deprecated and you can remove any calls to it.'); + if (typeof featureToken !== 'string' && !(featureToken instanceof String)) { + throw new Error("featureToken should be a string"); + } + + realmConstructor.Sync._setFeatureToken(featureToken.trim()); } } diff --git a/lib/index.d.ts b/lib/index.d.ts index f7fa3cdd..21f0e93a 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -448,10 +448,6 @@ declare namespace Realm.Sync { function removeListener(regex: string, name: string, changeCallback: (changeEvent: ChangeEvent) => void): Promise; function setLogLevel(logLevel: 'all' | 'trace' | 'debug' | 'detail' | 'info' | 'warn' | 'error' | 'fatal' | 'off'): void; function initiateClientReset(path: string): void; - - /** - * @deprecated, to be removed in future versions - */ function setFeatureToken(token: string): void; type Instruction = { From dfc6aea6a6089acbac347f2a151084c53b5c2626 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Thu, 8 Mar 2018 17:27:13 +0100 Subject: [PATCH 37/39] Deprecated setFeatureToken(). (#1694) --- CHANGELOG.md | 1 + lib/extensions.js | 6 +----- lib/index.d.ts | 4 ++++ 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da96615f..34b157a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ - Method `Realm.privilges()` to compute privileges on a Realm, a Realm object schema, or a Realm object. The method returns either a `Realm.Permissions.Realm` or `Realm.Permissions.Class` object. - For non-synced Realms, all privileges are always granted. - For more details, please read the reference documentation. +* [Sync] Decrepated `Realm.Sync.setFeatureToken` (#1689). ### Internal * Updated to Realm Core 5.3.0. diff --git a/lib/extensions.js b/lib/extensions.js index 033d305c..2f3792f4 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -151,11 +151,7 @@ module.exports = function(realmConstructor) { if (realmConstructor.Sync._setFeatureToken) { realmConstructor.Sync.setFeatureToken = function(featureToken) { - if (typeof featureToken !== 'string' && !(featureToken instanceof String)) { - throw new Error("featureToken should be a string"); - } - - realmConstructor.Sync._setFeatureToken(featureToken.trim()); + console.log('Realm.Sync.setFeatureToken() is deprecated and you can remove any calls to it.'); } } diff --git a/lib/index.d.ts b/lib/index.d.ts index 21f0e93a..f7fa3cdd 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -448,6 +448,10 @@ declare namespace Realm.Sync { function removeListener(regex: string, name: string, changeCallback: (changeEvent: ChangeEvent) => void): Promise; function setLogLevel(logLevel: 'all' | 'trace' | 'debug' | 'detail' | 'info' | 'warn' | 'error' | 'fatal' | 'off'): void; function initiateClientReset(path: string): void; + + /** + * @deprecated, to be removed in future versions + */ function setFeatureToken(token: string): void; type Instruction = { From 945181b3abec6b056ce98cad105d28fe199f0c9a Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Fri, 9 Mar 2018 09:56:30 +0100 Subject: [PATCH 38/39] Updating to Realm Sync 3.0.0-rc.1 and Realm Core 5.4.0. (#1698) --- CHANGELOG.md | 7 +++++-- dependencies.list | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34b157a7..dccdc632 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,9 +25,12 @@ - For more details, please read the reference documentation. * [Sync] Decrepated `Realm.Sync.setFeatureToken` (#1689). +### Bug fixes +* Fixed usage of disk space preallocation which would occasionally fail on recent MacOS running with the APFS filesystem (Realm Core #3005). + ### Internal -* Updated to Realm Core 5.3.0. -* Updated to Realm Sync 3.0.0-beta.10. +* Updated to Realm Core 5.4.0. +* Updated to Realm Sync 3.0.0-rc.1. * Tested against Realm Object Server 3.0.0-alpha.8. diff --git a/dependencies.list b/dependencies.list index 9dca6ef3..1247f305 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js VERSION=2.3.0-beta.2 -REALM_CORE_VERSION=5.3.0 -REALM_SYNC_VERSION=3.0.0-beta.10 +REALM_CORE_VERSION=5.4.0 +REALM_SYNC_VERSION=3.0.0-rc.1 REALM_OBJECT_SERVER_VERSION=3.0.0-alpha.8 From abb04e399be2a6a47559fa507bdc824990b4bd6f Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Fri, 9 Mar 2018 10:40:45 +0100 Subject: [PATCH 39/39] Default sync realm (#1693) * Configuration for default Realm. * Use url-parse to make RN happy. --- CHANGELOG.md | 1 + docs/realm.js | 9 +++++++++ lib/extensions.js | 24 ++++++++++++++++++++++++ lib/index.d.ts | 7 ++++++- package.json | 2 +- tests/js/realm-tests.js | 2 +- tests/js/session-tests.js | 27 +++++++++++++++++++++++++++ 7 files changed, 69 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dccdc632..0fcd769e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ - Method `Realm.privilges()` to compute privileges on a Realm, a Realm object schema, or a Realm object. The method returns either a `Realm.Permissions.Realm` or `Realm.Permissions.Class` object. - For non-synced Realms, all privileges are always granted. - For more details, please read the reference documentation. +* Added `Realm.defaultSyncConfiguration()` which will return the configuration for a default synced Realm (#1688). * [Sync] Decrepated `Realm.Sync.setFeatureToken` (#1689). ### Bug fixes diff --git a/docs/realm.js b/docs/realm.js index 359a714a..1959151e 100644 --- a/docs/realm.js +++ b/docs/realm.js @@ -119,6 +119,15 @@ class Realm { */ static openAsync(config, callback, progressCallback) {} + /** + * Return a configuration for a default synced Realm. The server URL for the current user will be used as base for + * the URL for the synced Realm. + * @throws {Error} if zero or multiple users are logged in + * @returns {Realm~Configuration} - a configuration matching a default synced Realm. + * @since 2.3.0 + */ + static defaultSyncConfiguration() {} + /** * Closes this Realm so it may be re-opened with a newer schema version. * All objects and collections from this Realm are no longer valid after calling this method. diff --git a/lib/extensions.js b/lib/extensions.js index 2f3792f4..93d08322 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -18,6 +18,8 @@ 'use strict'; +const URL = require('url-parse'); + let getOwnPropertyDescriptors = Object.getOwnPropertyDescriptors || function(obj) { return Object.getOwnPropertyNames(obj).reduce(function (descriptors, name) { descriptors[name] = Object.getOwnPropertyDescriptor(obj, name); @@ -149,6 +151,28 @@ module.exports = function(realmConstructor) { setConstructorOnPrototype(realmConstructor.Sync.User); setConstructorOnPrototype(realmConstructor.Sync.Session); + // A configuration for a default Realm + realmConstructor.defaultSyncConfiguration = function() { + let users = this.Sync.User.all; + let identities = Object.keys(users); + if (identities.length === 1) { + let user = users[identities[0]]; + let url = new URL(user.server); + let secure = (url.protocol === 'https:')?'s':''; + let port = (url.port === undefined)?'9080':url.port + let realmUrl = `realm${secure}://${url.hostname}:${port}/~/default`; + + let config = { + sync: { + user, + url: realmUrl + } + }; + return config; + } + new Error(`One and only one user should be logged in but found ${users.length} users.`); + } + if (realmConstructor.Sync._setFeatureToken) { realmConstructor.Sync.setFeatureToken = function(featureToken) { console.log('Realm.Sync.setFeatureToken() is deprecated and you can remove any calls to it.'); diff --git a/lib/index.d.ts b/lib/index.d.ts index f7fa3cdd..79730398 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -565,11 +565,16 @@ declare class Realm { */ static openAsync(config: Realm.Configuration, callback: (error: any, realm: Realm) => void, progressCallback?: Realm.Sync.ProgressNotificationCallback): void + /** + * Return a configuration for a default Realm. + */ + static defaultSyncConfiguration(): string; + /** * Delete the Realm file for the given configuration. * @param {Configuration} config */ - static deleteFile(config: Realm.Configuration): void + static deleteFile(config: Realm.Configuration): void; /** * @param {Realm.Configuration} config? diff --git a/package.json b/package.json index 842cb48b..fc8c2276 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "request": "^2.78.0", "stream-counter": "^1.0.0", "sync-request": "^3.0.1", - "url-parse": "^1.1.7" + "url-parse": "^1.2.0" }, "devDependencies": { "@types/node": "^4.0.35", diff --git a/tests/js/realm-tests.js b/tests/js/realm-tests.js index 61a07b66..9fe9ab76 100644 --- a/tests/js/realm-tests.js +++ b/tests/js/realm-tests.js @@ -56,7 +56,7 @@ module.exports = { const realm2 = new Realm({schema: [], path: testPath2}); TestCase.assertEqual(realm2.path, defaultDir + testPath2); }, - + testRealmIsClosed: function() { const realm = new Realm({schema: []}); TestCase.assertFalse(realm.isClosed); diff --git a/tests/js/session-tests.js b/tests/js/session-tests.js index ee01c3c6..97c8db41 100644 --- a/tests/js/session-tests.js +++ b/tests/js/session-tests.js @@ -166,6 +166,33 @@ module.exports = { }); }, + testDefaultRealm() { + if (!isNodeProccess) { + return; + } + + const username = uuid(); + const realmName = 'default'; + const expectedObjectsCount = 3; + + let user; + return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) + .then(() => Realm.Sync.User.login('http://localhost:9080', username, 'password')) + .then(u => { + user = u; + return Realm.open(Realm.defaultSyncConfiguration()); + }) + .then(realm => { + let actualObjectsCount = realm.objects('Dog').length; + TestCase.assertEqual(actualObjectsCount, expectedObjectsCount, "Synced realm does not contain the expected objects count"); + + const session = realm.syncSession; + TestCase.assertInstanceOf(session, Realm.Sync.Session); + TestCase.assertEqual(session.user.identity, user.identity); + TestCase.assertEqual(session.state, 'active'); + }); + }, + testRealmOpenWithExistingLocalRealm() { if (!isNodeProccess) { return;