diff --git a/CHANGELOG.md b/CHANGELOG.md index e6a601ed..554efc68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,8 @@ X.Y.Z Release notes * Added support for aggregate functions on `Realm.Results` and `Realm.List` of primitive types. ### Bug fixes -* None +* Avoid closing then reopening a sync session when using `Realm.open` (#1391). +* Respect custom Realm paths when using `Realm.open` (#1392 and #1393). ### Internal * Upgrading to Realm Sync 2.0.0-rc28. diff --git a/lib/browser/index.js b/lib/browser/index.js index cc6e7e27..61dd013c 100644 --- a/lib/browser/index.js +++ b/lib/browser/index.js @@ -128,6 +128,7 @@ util.createMethods(Realm.prototype, objectTypes.REALM, [ 'removeListener', 'removeAllListeners', 'close', + '_waitForDownload', ]); // Mutating methods: @@ -189,12 +190,6 @@ Object.defineProperties(Realm, { rpc.clearTestState(); }, }, - _waitForDownload: { - value: function(_config, sessionCallback, callback) { - sessionCallback(); - callback(); - } - }, }); for (let i = 0, len = debugHosts.length; i < len; i++) { diff --git a/lib/extensions.js b/lib/extensions.js index 1e21e602..3233ae4d 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -31,6 +31,25 @@ function setConstructorOnPrototype(klass) { } } +// Return a configuration usable by `Realm.open` when waiting for a download. +// It must have caching disabled, and no schema or schema version specified. +function waitForDownloadConfig(config) { + if (!config) { + return {_cache: false}; + } + + if (typeof config == 'string') { + return {path: config, _cache: false}; + } + + if (typeof config == 'object') { + return Object.assign({}, config, {schema: undefined, schemaVersion: undefined, _cache: false}); + } + + // Unknown type. Pass the config through. + return config; +} + module.exports = function(realmConstructor) { // Add the specified Array methods to the Collection prototype. Object.defineProperties(realmConstructor.Collection.prototype, require('./collection-methods')); @@ -45,7 +64,8 @@ module.exports = function(realmConstructor) { open(config) { let syncSession; let promise = new Promise((resolve, reject) => { - realmConstructor._waitForDownload(config, + let realm = new realmConstructor(waitForDownloadConfig(config)); + realm._waitForDownload( (session) => { syncSession = session; }, @@ -55,7 +75,7 @@ module.exports = function(realmConstructor) { } else { try { - let syncedRealm = new this(config); + let syncedRealm = new realmConstructor(config); //FIXME: RN hangs here. Remove when node's makeCallback alternative is implemented setTimeout(() => { resolve(syncedRealm); }, 1); } catch (e) { @@ -80,26 +100,16 @@ module.exports = function(realmConstructor) { const message = "Realm.openAsync is now deprecated in favor of Realm.open. This function will be removed in future versions."; (console.warn || console.log).call(console, message); - realmConstructor._waitForDownload(config, - (syncSession) => { - if (progressCallback) { - syncSession.addProgressNotification('download', 'forCurrentlyOutstandingWork', progressCallback); - } - }, - (error) => { - if (error) { - setTimeout(() => { callback(error); }, 1); - } - else { - try { - let syncedRealm = new this(config); - //FIXME: RN hangs here. Remove when node's makeCallback alternative is implemented - setTimeout(() => { callback(null, syncedRealm); }, 1); - } catch (e) { - setTimeout(() => { callback(e); }, 1); - } - } - }); + let promise = this.open(config) + if (progressCallback) { + promise.progress(progressCallback) + } + + promise.then(realm => { + callback(null, realm) + }).catch(error => { + callback(error); + }); }, })); diff --git a/lib/permission-api.js b/lib/permission-api.js index 4cdff8da..71caf39b 100644 --- a/lib/permission-api.js +++ b/lib/permission-api.js @@ -72,31 +72,11 @@ function getSpecialPurposeRealm(user, realmName, schema) { } }; - const _Realm = user.constructor._realmConstructor; - - return new Promise((resolve, reject) => { - _Realm._waitForDownload(config, (error) => { - - // FIXME: I don't understand why, but removing the following setTimeout causes the subsequent - // setTimeout call (when resolving the promise) to hang on RN iOS. - // This might be related to our general makeCallback issue: #1255. - setTimeout(() => {}, 1); - - if (error) { - setTimeout(() => reject(error), 1); - } - else { - try { - let syncedRealm = new _Realm(config); - user[specialPurposeRealmsKey][realmName] = syncedRealm; - //FIXME: RN hangs here. Remove when node's makeCallback alternative is implemented (#1255) - setTimeout(() => resolve(syncedRealm), 1); - } catch (e) { - setTimeout(() => reject(e), 1); - } - } - }); - }); + let Realm = user.constructor._realmConstructor; + return Realm.open(config).then(realm => { + user[specialPurposeRealmsKey][realmName] = realm; + return realm; + }) } function createInManagementRealm(user, modelName, modelInitializer) { diff --git a/src/js_realm.hpp b/src/js_realm.hpp index e94e42be..d6b164d8 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -218,7 +218,6 @@ public: {"clearTestState", wrap}, {"copyBundledRealmFiles", wrap}, {"deleteFile", wrap}, - {"_waitForDownload", wrap}, }; PropertyMap const static_properties = { @@ -241,6 +240,7 @@ public: {"close", wrap}, {"compact", wrap}, {"deleteModel", wrap}, + {"_waitForDownload", wrap}, #if REALM_ENABLE_SYNC {"_subscribeToObjects", wrap}, #endif @@ -503,6 +503,12 @@ void RealmClass::constructor(ContextType ctx, ObjectType this_object, size_t realm_ptr->reset(); }; } + + static const String cache_string = "_cache"; + ValueType cache_value = Object::get_property(ctx, object, cache_string); + if (!Value::is_undefined(ctx, cache_value)) { + config.cache = Value::validated_to_boolean(ctx, cache_value, "_cache"); + } } } else { @@ -694,30 +700,17 @@ void RealmClass::get_sync_session(ContextType ctx, ObjectType object, ReturnV template void RealmClass::wait_for_download_completion(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) { - args.validate_maximum(3); - auto config_object = Value::validated_to_object(ctx, args[0]); - auto callback_function = Value::validated_to_function(ctx, args[1 + (args.count == 3)]); + args.validate_maximum(2); + auto callback_function = Value::validated_to_function(ctx, args[0 + (args.count == 2)]); ValueType session_callback = Value::from_null(ctx); - if (args.count == 3) { - session_callback = Value::validated_to_function(ctx, args[1]); + if (args.count == 2) { + session_callback = Value::validated_to_function(ctx, args[0]); } #if REALM_ENABLE_SYNC - ValueType sync_config_value = Object::get_property(ctx, config_object, "sync"); - if (!Value::is_undefined(ctx, sync_config_value)) { - realm::Realm::Config config; - config.cache = false; - static const String encryption_key_string = "encryptionKey"; - ValueType encryption_key_value = Object::get_property(ctx, config_object, encryption_key_string); - if (!Value::is_undefined(ctx, encryption_key_value)) { - auto encryption_key = Value::validated_to_binary(ctx, encryption_key_value, "encryptionKey"); - config.encryption_key.assign(encryption_key.data(), encryption_key.data() + encryption_key.size()); - } - - Protected thiz(ctx, this_object); - SyncClass::populate_sync_config(ctx, thiz, config_object, config); - + auto realm = *get_internal>(this_object); + if (auto* sync_config = realm->config().sync_config.get()) { Protected protected_callback(ctx, callback_function); Protected protected_this(ctx, this_object); Protected protected_ctx(Context::get_global_context(ctx)); @@ -738,85 +731,39 @@ void RealmClass::wait_for_download_completion(ContextType ctx, ObjectType thi callback_arguments[0] = object; Function::callback(protected_ctx, protected_callback, protected_this, 1, callback_arguments); } + + // We keep our Realm instance alive until the callback has had a chance to open its own instance. + // This allows it to share the sync session that our Realm opened. + if (realm) + realm->close(); }); - std::function waitFunc = std::move(wait_handler); - std::function progressFunc; - - SharedRealm realm; - try { - realm = realm::Realm::get_shared_realm(config); - } - catch (const RealmFileException& ex) { - handleRealmFileException(ctx, config, ex); - } - catch (...) { - throw; - } - - if (auto sync_config = config.sync_config) - { - static const String progressFuncName = "_onDownloadProgress"; - bool progressFuncDefined = false; - if (!Value::is_boolean(ctx, sync_config_value) && !Value::is_undefined(ctx, sync_config_value)) - { - auto sync_config_object = Value::validated_to_object(ctx, sync_config_value); - - ValueType progressFuncValue = Object::get_property(ctx, sync_config_object, progressFuncName); - progressFuncDefined = !Value::is_undefined(ctx, progressFuncValue); - - if (progressFuncDefined) - { - Protected protected_progressCallback(protected_ctx, Value::validated_to_function(protected_ctx, progressFuncValue)); - EventLoopDispatcher progress_handler([=](uint64_t transferred_bytes, uint64_t transferrable_bytes) { - HANDLESCOPE - ValueType callback_arguments[2]; - callback_arguments[0] = Value::from_number(protected_ctx, transferred_bytes); - callback_arguments[1] = Value::from_number(protected_ctx, transferrable_bytes); - - Function::callback(protected_ctx, protected_progressCallback, protected_this, 2, callback_arguments); - }); - - progressFunc = std::move(progress_handler); + std::shared_ptr user = sync_config->user; + if (user && user->state() != SyncUser::State::Error) { + if (auto session = user->session_for_on_disk_path(realm->config().path)) { + if (!Value::is_null(ctx, session_callback)) { + FunctionType session_callback_func = Value::to_function(ctx, session_callback); + auto syncSession = create_object>(ctx, new WeakSession(session)); + ValueType callback_arguments[1]; + callback_arguments[0] = syncSession; + Function::callback(protected_ctx, session_callback_func, protected_this, 1, callback_arguments); } + + session->wait_for_download_completion(std::move(wait_handler)); + return; } - - std::shared_ptr user = sync_config->user; - if (user && user->state() != SyncUser::State::Error) { - if (auto session = user->session_for_on_disk_path(config.path)) { - if (!Value::is_null(ctx, session_callback)) { - FunctionType session_callback_func = Value::to_function(ctx, session_callback); - auto syncSession = create_object>(ctx, new WeakSession(session)); - ValueType callback_arguments[1]; - callback_arguments[0] = syncSession; - Function::callback(protected_ctx, session_callback_func, protected_this, 1, callback_arguments); - } - - if (progressFuncDefined) { - session->register_progress_notifier(std::move(progressFunc), SyncSession::NotifierType::download, false); - } - - session->wait_for_download_completion([=](std::error_code error_code) { - realm->close(); //capture and keep realm instance for until here - waitFunc(error_code); - }); - return; - } - } - - ObjectType object = Object::create_empty(protected_ctx); - Object::set_property(protected_ctx, object, "message", - Value::from_string(protected_ctx, "Cannot asynchronously open synced Realm because the associated session previously experienced a fatal error")); - Object::set_property(protected_ctx, object, "errorCode", Value::from_number(protected_ctx, 1)); - - ValueType callback_arguments[1]; - callback_arguments[0] = object; - Function::callback(protected_ctx, protected_callback, protected_this, 1, callback_arguments); - return; } + + ObjectType object = Object::create_empty(protected_ctx); + Object::set_property(protected_ctx, object, "message", + Value::from_string(protected_ctx, "Cannot asynchronously open synced Realm because the associated session previously experienced a fatal error")); + Object::set_property(protected_ctx, object, "errorCode", Value::from_number(protected_ctx, 1)); + + ValueType callback_arguments[1]; + callback_arguments[0] = object; + Function::callback(protected_ctx, protected_callback, protected_this, 1, callback_arguments); + return; } -#else - static_cast(config_object); #endif Function::callback(ctx, callback_function, this_object, 0, nullptr); diff --git a/tests/js/session-tests.js b/tests/js/session-tests.js index 0e87eeb6..3b453b3f 100644 --- a/tests/js/session-tests.js +++ b/tests/js/session-tests.js @@ -249,89 +249,6 @@ module.exports = { }); }, - testProgressNotificationsForRealmOpen() { - if (!isNodeProccess) { - return Promise.resolve(); - } - - const username = uuid(); - const realmName = uuid(); - 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 => { - const accessTokenRefreshed = this; - let successCounter = 0; - let progressNotificationCalled = false; - let config = { - sync: { - user, - url: `realm://localhost:9080/~/${realmName}`, - _onDownloadProgress: (transferred, total) => { - progressNotificationCalled = true - }, - }, - schema: [{ name: 'Dog', properties: { name: 'string' } }], - }; - - return Realm.open(config) - .then(realm => { - return realm.syncSession; - }).then(session => { - TestCase.assertTrue(progressNotificationCalled, "Progress notification not called for Realm.open"); - }); - }); - }); - }, - - testProgressNotificationsForRealmOpenAsync() { - if (!isNodeProccess) { - return Promise.resolve(); - } - - const username = uuid(); - const realmName = uuid(); - 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) => { - let progressNotificationCalled = false; - let config = { - sync: { user, url: `realm://localhost:9080/~/${realmName}`, - _onDownloadProgress: (transferred, total) => { - progressNotificationCalled = true - }, - }, - schema: [{ name: 'Dog', properties: { name: 'string' } }], - }; - - Realm.openAsync(config, (error, realm) => { - try { - if (error) { - reject(error); - } - - setTimeout(() => { - try { - TestCase.assertTrue(progressNotificationCalled, "Progress notification not called for Realm.openAsync"); - resolve(); - } catch (e) { - reject(e); - } - }, 50); - } - catch (e) { - reject(e); - } - }); - }); - }); - }); - }, - testRealmOpenAsyncNoSchema() { if (!isNodeProccess) { return Promise.resolve(); @@ -697,7 +614,7 @@ module.exports = { }); }, - testProgressNotificationsForRealmOpen2() { + testProgressNotificationsForRealmOpen() { if (!isNodeProccess) { return Promise.resolve(); } @@ -736,7 +653,7 @@ module.exports = { }); }, - testProgressNotificationsForRealmOpenAsync2() { + testProgressNotificationsForRealmOpenAsync() { if (!isNodeProccess) { return Promise.resolve(); }