Restore support for opening query-based sync Realms with a dynamic schema (#2065)
* Restore support for opening query-based sync Realms with a dynamic schema * Adjust how the schema is extended for query-based Realms * Register constructors for permissions types even no schema is supplied * Remove some cruft from tests * Fix a use-after-free in dynamic schema mode * Fix a test
This commit is contained in:
parent
10f72c5444
commit
bfb06ac0df
|
@ -33,25 +33,6 @@ 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds the permissions associated with a given Role or create them as needed.
|
* Finds the permissions associated with a given Role or create them as needed.
|
||||||
*
|
*
|
||||||
|
@ -118,23 +99,14 @@ module.exports = function(realmConstructor) {
|
||||||
|
|
||||||
let syncSession;
|
let syncSession;
|
||||||
let promise = new Promise((resolve, reject) => {
|
let promise = new Promise((resolve, reject) => {
|
||||||
let realm = new realmConstructor(waitForDownloadConfig(config));
|
syncSession = realmConstructor._asyncOpen(config, (realm, error) => {
|
||||||
realm._waitForDownload(
|
if (error) {
|
||||||
(session) => { syncSession = session; },
|
setTimeout(() => { reject(error); }, 1);
|
||||||
(error) => {
|
}
|
||||||
realm.close();
|
else {
|
||||||
if (error) {
|
setTimeout(() => { resolve(realm); }, 1);
|
||||||
setTimeout(() => { reject(error); }, 1);
|
}
|
||||||
}
|
});
|
||||||
else {
|
|
||||||
try {
|
|
||||||
let syncedRealm = new realmConstructor(config);
|
|
||||||
setTimeout(() => { resolve(syncedRealm); }, 1);
|
|
||||||
} catch (e) {
|
|
||||||
reject(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
promise.progress = (callback) => {
|
promise.progress = (callback) => {
|
||||||
|
@ -453,42 +425,19 @@ module.exports = function(realmConstructor) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
Object.defineProperties(realmConstructor, getOwnPropertyDescriptors({
|
||||||
|
_extendQueryBasedSchema(schema) {
|
||||||
|
addSchemaIfNeeded(schema, realmConstructor.Permissions.Class);
|
||||||
|
addSchemaIfNeeded(schema, realmConstructor.Permissions.Permission);
|
||||||
|
addSchemaIfNeeded(schema, realmConstructor.Permissions.Realm);
|
||||||
|
addSchemaIfNeeded(schema, realmConstructor.Permissions.Role);
|
||||||
|
addSchemaIfNeeded(schema, realmConstructor.Permissions.User);
|
||||||
|
addSchemaIfNeeded(schema, realmConstructor.Subscription.ResultSets);
|
||||||
|
},
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Realm instance methods that are always available
|
|
||||||
Object.defineProperties(realmConstructor.prototype, getOwnPropertyDescriptors({
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extra internal constructor callback called by the C++ side.
|
|
||||||
* Used to work around the fact that we cannot override the original constructor,
|
|
||||||
* but still need to modify any input config.
|
|
||||||
*/
|
|
||||||
_constructor(config) {
|
|
||||||
// Even though this runs code only available for Sync it requires some serious misconfiguration
|
|
||||||
// for this to happen
|
|
||||||
if (config && config.sync) {
|
|
||||||
if (!Realm.Sync) {
|
|
||||||
throw new Error("Realm is not compiled with Sync, but the configuration contains sync features.");
|
|
||||||
}
|
|
||||||
// Only inject schemas on query-based Realms
|
|
||||||
if (config.sync.partial === true || config.sync.fullSynchronization === false) {
|
|
||||||
if (!config.schema) {
|
|
||||||
config['schema'] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
addSchemaIfNeeded(config.schema, realmConstructor.Permissions.Class);
|
|
||||||
addSchemaIfNeeded(config.schema, realmConstructor.Permissions.Permission);
|
|
||||||
addSchemaIfNeeded(config.schema, realmConstructor.Permissions.Realm);
|
|
||||||
addSchemaIfNeeded(config.schema, realmConstructor.Permissions.Role);
|
|
||||||
addSchemaIfNeeded(config.schema, realmConstructor.Permissions.User);
|
|
||||||
addSchemaIfNeeded(config.schema, realmConstructor.Subscription.ResultSets);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
// TODO: Remove this now useless object.
|
// TODO: Remove this now useless object.
|
||||||
var types = Object.freeze({
|
var types = Object.freeze({
|
||||||
'BOOL': 'bool',
|
'BOOL': 'bool',
|
||||||
|
|
190
src/js_realm.hpp
190
src/js_realm.hpp
|
@ -228,7 +228,7 @@ public:
|
||||||
static void commit_transaction(ContextType, ObjectType, Arguments, ReturnValue&);
|
static void commit_transaction(ContextType, ObjectType, Arguments, ReturnValue&);
|
||||||
static void cancel_transaction(ContextType, ObjectType, Arguments, ReturnValue&);
|
static void cancel_transaction(ContextType, ObjectType, Arguments, ReturnValue&);
|
||||||
static void add_listener(ContextType, ObjectType, Arguments, ReturnValue &);
|
static void add_listener(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||||
static void wait_for_download_completion(ContextType, ObjectType, Arguments, ReturnValue &);
|
static void async_open_realm(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||||
static void remove_listener(ContextType, ObjectType, Arguments, ReturnValue &);
|
static void remove_listener(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||||
static void remove_all_listeners(ContextType, ObjectType, Arguments, ReturnValue &);
|
static void remove_all_listeners(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||||
static void close(ContextType, ObjectType, Arguments, ReturnValue &);
|
static void close(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||||
|
@ -255,6 +255,8 @@ public:
|
||||||
// static methods
|
// static methods
|
||||||
static void constructor(ContextType, ObjectType, size_t, const ValueType[]);
|
static void constructor(ContextType, ObjectType, size_t, const ValueType[]);
|
||||||
static SharedRealm create_shared_realm(ContextType, realm::Realm::Config, bool, ObjectDefaultsMap &&, ConstructorMap &&);
|
static SharedRealm create_shared_realm(ContextType, realm::Realm::Config, bool, ObjectDefaultsMap &&, ConstructorMap &&);
|
||||||
|
static bool get_realm_config(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[],
|
||||||
|
Realm::Config& config, ObjectDefaultsMap& defaults, ConstructorMap& constructors);
|
||||||
|
|
||||||
static void schema_version(ContextType, ObjectType, Arguments, ReturnValue &);
|
static void schema_version(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||||
static void clear_test_state(ContextType, ObjectType, Arguments, ReturnValue &);
|
static void clear_test_state(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||||
|
@ -272,6 +274,9 @@ public:
|
||||||
{"clearTestState", wrap<clear_test_state>},
|
{"clearTestState", wrap<clear_test_state>},
|
||||||
{"copyBundledRealmFiles", wrap<copy_bundled_realm_files>},
|
{"copyBundledRealmFiles", wrap<copy_bundled_realm_files>},
|
||||||
{"deleteFile", wrap<delete_file>},
|
{"deleteFile", wrap<delete_file>},
|
||||||
|
#if REALM_ENABLE_SYNC
|
||||||
|
{"_asyncOpen", wrap<async_open_realm>},
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
PropertyMap<T> const static_properties = {
|
PropertyMap<T> const static_properties = {
|
||||||
|
@ -298,9 +303,6 @@ public:
|
||||||
{"privileges", wrap<privileges>},
|
{"privileges", wrap<privileges>},
|
||||||
{"_objectForObjectId", wrap<object_for_object_id>},
|
{"_objectForObjectId", wrap<object_for_object_id>},
|
||||||
{"_schemaName", wrap<get_schema_name_from_object>},
|
{"_schemaName", wrap<get_schema_name_from_object>},
|
||||||
#if REALM_ENABLE_SYNC
|
|
||||||
{"_waitForDownload", wrap<wait_for_download_completion>},
|
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
PropertyMap<T> const properties = {
|
PropertyMap<T> const properties = {
|
||||||
|
@ -374,6 +376,11 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Beginning a read transaction may reread the schema, invalidating the
|
||||||
|
// pointer that we're returning. Avoid this by ensuring that we're in a
|
||||||
|
// read transaction before we search the schema.
|
||||||
|
realm->read_group();
|
||||||
|
|
||||||
auto &schema = realm->schema();
|
auto &schema = realm->schema();
|
||||||
auto object_schema = schema.find(object_type);
|
auto object_schema = schema.find(object_type);
|
||||||
|
|
||||||
|
@ -440,27 +447,18 @@ static inline void convert_outdated_datetime_columns(const SharedRealm &realm) {
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void RealmClass<T>::constructor(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[]) {
|
bool RealmClass<T>::get_realm_config(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[],
|
||||||
|
Realm::Config& config, ObjectDefaultsMap& defaults, ConstructorMap& constructors) {
|
||||||
if (argc > 1) {
|
if (argc > 1) {
|
||||||
throw std::runtime_error("Invalid arguments when constructing 'Realm'");
|
throw std::runtime_error("Invalid arguments when constructing 'Realm'");
|
||||||
}
|
}
|
||||||
// Callback to custom constructor
|
|
||||||
// This is used to work around the fact that we cannot reliably wrap the the Realm constructor
|
|
||||||
// without risking breaking existing code, so instead we make an extra roundtrip to this method.
|
|
||||||
ValueType modifiedConfig = Object::call_method(ctx, this_object, "_constructor", argc, arguments);
|
|
||||||
|
|
||||||
// Continue with C++ construction
|
|
||||||
realm::Realm::Config config;
|
|
||||||
ObjectDefaultsMap defaults;
|
|
||||||
ConstructorMap constructors;
|
|
||||||
bool schema_updated = false;
|
bool schema_updated = false;
|
||||||
|
if (argc == 0) {
|
||||||
if (Value::is_undefined(ctx, modifiedConfig)) {
|
|
||||||
config.path = default_path();
|
config.path = default_path();
|
||||||
}
|
}
|
||||||
else if (argc == 1) {
|
else if (argc == 1) {
|
||||||
ValueType value = modifiedConfig;
|
ValueType value = arguments[0];
|
||||||
if (Value::is_string(ctx, value)) {
|
if (Value::is_string(ctx, value)) {
|
||||||
config.path = Value::validated_to_string(ctx, value, "path");
|
config.path = Value::validated_to_string(ctx, value, "path");
|
||||||
}
|
}
|
||||||
|
@ -513,6 +511,14 @@ void RealmClass<T>::constructor(ContextType ctx, ObjectType this_object, size_t
|
||||||
ValueType schema_value = Object::get_property(ctx, object, schema_string);
|
ValueType schema_value = Object::get_property(ctx, object, schema_string);
|
||||||
if (!Value::is_undefined(ctx, schema_value)) {
|
if (!Value::is_undefined(ctx, schema_value)) {
|
||||||
ObjectType schema_object = Value::validated_to_array(ctx, schema_value, "schema");
|
ObjectType schema_object = Value::validated_to_array(ctx, schema_value, "schema");
|
||||||
|
#if REALM_ENABLE_SYNC
|
||||||
|
// Ensure that the permissions and ResultSets object definitions
|
||||||
|
// are present in the schema for query-based sync
|
||||||
|
if (config.sync_config && config.sync_config->is_partial) {
|
||||||
|
auto realm_constructor = Value::validated_to_object(ctx, Object::get_global(ctx, "Realm"));
|
||||||
|
Object::call_method(ctx, realm_constructor, "_extendQueryBasedSchema", 1, &schema_value);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
config.schema.emplace(Schema<T>::parse_schema(ctx, schema_object, defaults, constructors));
|
config.schema.emplace(Schema<T>::parse_schema(ctx, schema_object, defaults, constructors));
|
||||||
schema_updated = true;
|
schema_updated = true;
|
||||||
}
|
}
|
||||||
|
@ -600,7 +606,15 @@ void RealmClass<T>::constructor(ContextType ctx, ObjectType this_object, size_t
|
||||||
|
|
||||||
config.path = normalize_realm_path(config.path);
|
config.path = normalize_realm_path(config.path);
|
||||||
ensure_directory_exists_for_file(config.path);
|
ensure_directory_exists_for_file(config.path);
|
||||||
|
return schema_updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void RealmClass<T>::constructor(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[]) {
|
||||||
|
realm::Realm::Config config;
|
||||||
|
ObjectDefaultsMap defaults;
|
||||||
|
ConstructorMap constructors;
|
||||||
|
bool schema_updated = get_realm_config(ctx, this_object, argc, arguments, config, defaults, constructors);
|
||||||
auto realm = create_shared_realm(ctx, config, schema_updated, std::move(defaults), std::move(constructors));
|
auto realm = create_shared_realm(ctx, config, schema_updated, std::move(defaults), std::move(constructors));
|
||||||
|
|
||||||
// Fix for datetime -> timestamp conversion
|
// Fix for datetime -> timestamp conversion
|
||||||
|
@ -611,7 +625,7 @@ void RealmClass<T>::constructor(ContextType ctx, ObjectType this_object, size_t
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
SharedRealm RealmClass<T>::create_shared_realm(ContextType ctx, realm::Realm::Config config, bool schema_updated,
|
SharedRealm RealmClass<T>::create_shared_realm(ContextType ctx, realm::Realm::Config config, bool schema_updated,
|
||||||
ObjectDefaultsMap&& defaults, ConstructorMap&& constructors) {
|
ObjectDefaultsMap&& defaults, ConstructorMap&& constructors) {
|
||||||
config.execution_context = Context<T>::get_execution_context_id(ctx);
|
config.execution_context = Context<T>::get_execution_context_id(ctx);
|
||||||
|
|
||||||
SharedRealm realm;
|
SharedRealm realm;
|
||||||
|
@ -622,13 +636,6 @@ SharedRealm RealmClass<T>::create_shared_realm(ContextType ctx, realm::Realm::Co
|
||||||
handleRealmFileException(ctx, config, ex);
|
handleRealmFileException(ctx, config, ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if REALM_ENABLE_SYNC
|
|
||||||
auto schema = realm->schema();
|
|
||||||
if (realm->is_partial() && schema.empty() && config.cache) {
|
|
||||||
throw std::invalid_argument("Query-based sync requires a schema.");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
GlobalContextType global_context = Context<T>::get_global_context(ctx);
|
GlobalContextType global_context = Context<T>::get_global_context(ctx);
|
||||||
if (!realm->m_binding_context) {
|
if (!realm->m_binding_context) {
|
||||||
realm->m_binding_context.reset(new RealmDelegate<T>(realm, global_context));
|
realm->m_binding_context.reset(new RealmDelegate<T>(realm, global_context));
|
||||||
|
@ -643,6 +650,18 @@ SharedRealm RealmClass<T>::create_shared_realm(ContextType ctx, realm::Realm::Co
|
||||||
js_binding_context->m_defaults = std::move(defaults);
|
js_binding_context->m_defaults = std::move(defaults);
|
||||||
js_binding_context->m_constructors = std::move(constructors);
|
js_binding_context->m_constructors = std::move(constructors);
|
||||||
}
|
}
|
||||||
|
#if REALM_ENABLE_SYNC
|
||||||
|
// For query-based Realms we need to register the constructors for the
|
||||||
|
// permissions types even if a schema isn't specified
|
||||||
|
else if (config.sync_config && config.sync_config->is_partial && js_binding_context->m_constructors.empty()) {
|
||||||
|
ValueType schema_value = Object::create_array(ctx);
|
||||||
|
auto realm_constructor = Value::validated_to_object(ctx, Object::get_global(ctx, "Realm"));
|
||||||
|
Object::call_method(ctx, realm_constructor, "_extendQueryBasedSchema", 1, &schema_value);
|
||||||
|
Schema<T>::parse_schema(ctx, Value::to_object(ctx, schema_value), defaults, constructors);
|
||||||
|
js_binding_context->m_defaults = std::move(defaults);
|
||||||
|
js_binding_context->m_constructors = std::move(constructors);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
return realm;
|
return realm;
|
||||||
}
|
}
|
||||||
|
@ -801,70 +820,89 @@ void RealmClass<T>::get_sync_session(ContextType ctx, ObjectType object, ReturnV
|
||||||
|
|
||||||
#if REALM_ENABLE_SYNC
|
#if REALM_ENABLE_SYNC
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void RealmClass<T>::wait_for_download_completion(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
void RealmClass<T>::async_open_realm(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||||
args.validate_maximum(2);
|
args.validate_maximum(2);
|
||||||
auto callback_function = Value::validated_to_function(ctx, args[0 + (args.count == 2)]);
|
auto callback_function = Value::validated_to_function(ctx, args[0 + (args.count == 2)]);
|
||||||
|
Realm::Config config;
|
||||||
|
ObjectDefaultsMap defaults;
|
||||||
|
ConstructorMap constructors;
|
||||||
|
bool schema_updated = get_realm_config(ctx, this_object, args.count - 1, args.value, config, defaults, constructors);
|
||||||
|
|
||||||
ValueType session_callback = Value::from_null(ctx);
|
if (!config.sync_config) {
|
||||||
if (args.count == 2) {
|
throw std::logic_error("_asyncOpen can only be used on a synchronized Realm.");
|
||||||
session_callback = Value::validated_to_function(ctx, args[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto realm = *get_internal<T, RealmClass<T>>(this_object);
|
|
||||||
auto* sync_config = realm->config().sync_config.get();
|
|
||||||
if (!sync_config) {
|
|
||||||
throw std::logic_error("_waitForDownload can only be used on a synchronized Realm.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Protected<FunctionType> protected_callback(ctx, callback_function);
|
Protected<FunctionType> protected_callback(ctx, callback_function);
|
||||||
Protected<ObjectType> protected_this(ctx, this_object);
|
Protected<ObjectType> protected_this(ctx, this_object);
|
||||||
Protected<typename T::GlobalContext> protected_ctx(Context<T>::get_global_context(ctx));
|
Protected<typename T::GlobalContext> protected_ctx(Context<T>::get_global_context(ctx));
|
||||||
|
|
||||||
std::shared_ptr<SyncUser> user = sync_config->user;
|
auto& user = config.sync_config->user;
|
||||||
if (user && user->state() != SyncUser::State::Error) {
|
if (user && user->state() == SyncUser::State::Error) {
|
||||||
if (auto session = user->session_for_on_disk_path(realm->config().path)) {
|
ObjectType object = Object::create_empty(protected_ctx);
|
||||||
if (!Value::is_null(ctx, session_callback)) {
|
Object::set_property(protected_ctx, object, "message",
|
||||||
FunctionType session_callback_func = Value::to_function(ctx, session_callback);
|
Value::from_string(protected_ctx, "Cannot asynchronously open synced Realm because the associated session previously experienced a fatal error"));
|
||||||
auto syncSession = create_object<T, SessionClass<T>>(ctx, new WeakSession(session));
|
Object::set_property(protected_ctx, object, "errorCode", Value::from_number(protected_ctx, 1));
|
||||||
ValueType callback_arguments[1];
|
|
||||||
callback_arguments[0] = syncSession;
|
|
||||||
Function<T>::callback(protected_ctx, session_callback_func, typename T::Object(), 1, callback_arguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
EventLoopDispatcher<WaitHandler> wait_handler([=](std::error_code error_code) {
|
ValueType callback_arguments[1];
|
||||||
HANDLESCOPE
|
callback_arguments[0] = object;
|
||||||
if (!error_code) {
|
Function<T>::callback(protected_ctx, protected_callback, protected_this, 1, callback_arguments);
|
||||||
//success
|
|
||||||
Function<T>::callback(protected_ctx, protected_callback, typename T::Object(), 0, nullptr);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
//fail
|
|
||||||
ObjectType object = Object::create_empty(protected_ctx);
|
|
||||||
Object::set_property(protected_ctx, object, "message", Value::from_string(protected_ctx, error_code.message()));
|
|
||||||
Object::set_property(protected_ctx, object, "errorCode", Value::from_number(protected_ctx, error_code.value()));
|
|
||||||
|
|
||||||
ValueType callback_arguments[1];
|
|
||||||
callback_arguments[0] = object;
|
|
||||||
|
|
||||||
Function<T>::callback(protected_ctx, protected_callback, typename T::Object(), 1, callback_arguments);
|
|
||||||
}
|
|
||||||
// Ensure that the session remains alive until the callback has had an opportunity to reopen the Realm
|
|
||||||
// with the appropriate schema.
|
|
||||||
(void)session;
|
|
||||||
});
|
|
||||||
session->wait_for_download_completion(std::move(wait_handler));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ObjectType object = Object::create_empty(protected_ctx);
|
// First download the Realm with no schema set to avoid spurious writes that
|
||||||
Object::set_property(protected_ctx, object, "message",
|
// the server may either reject (for read-only Realms) or just waste time
|
||||||
Value::from_string(protected_ctx, "Cannot asynchronously open synced Realm because the associated session previously experienced a fatal error"));
|
// merging.
|
||||||
Object::set_property(protected_ctx, object, "errorCode", Value::from_number(protected_ctx, 1));
|
std::shared_ptr<Realm> realm;
|
||||||
|
try {
|
||||||
|
auto download_config = config;
|
||||||
|
download_config.schema = util::none;
|
||||||
|
download_config.cache = false;
|
||||||
|
realm = realm::Realm::get_shared_realm(std::move(download_config));
|
||||||
|
}
|
||||||
|
catch (const RealmFileException& ex) {
|
||||||
|
handleRealmFileException(ctx, config, ex);
|
||||||
|
}
|
||||||
|
|
||||||
ValueType callback_arguments[1];
|
auto session = user->session_for_on_disk_path(realm->config().path);
|
||||||
callback_arguments[0] = object;
|
EventLoopDispatcher<WaitHandler> wait_handler([=, config=std::move(config),
|
||||||
Function<T>::callback(protected_ctx, protected_callback, protected_this, 1, callback_arguments);
|
defaults=std::move(defaults),
|
||||||
|
constructors=std::move(constructors)](std::error_code error_code) mutable {
|
||||||
|
HANDLESCOPE
|
||||||
|
if (error_code) {
|
||||||
|
ObjectType object = Object::create_empty(protected_ctx);
|
||||||
|
Object::set_property(protected_ctx, object, "message", Value::from_string(protected_ctx, error_code.message()));
|
||||||
|
Object::set_property(protected_ctx, object, "errorCode", Value::from_number(protected_ctx, error_code.value()));
|
||||||
|
|
||||||
|
ValueType callback_arguments[2];
|
||||||
|
callback_arguments[0] = Value::from_null(protected_ctx);
|
||||||
|
callback_arguments[1] = object;
|
||||||
|
|
||||||
|
Function<T>::callback(protected_ctx, protected_callback, typename T::Object(), 2, callback_arguments);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that all of our metadata tables are properly initialized if
|
||||||
|
// this is a query-based sync Realm
|
||||||
|
if (config.sync_config->is_partial) {
|
||||||
|
realm->update_schema({}, 0);
|
||||||
|
}
|
||||||
|
realm->close();
|
||||||
|
|
||||||
|
// Reopen it with the real configuration and pass that Realm back to the callback
|
||||||
|
auto final_realm = create_shared_realm(ctx, std::move(config),
|
||||||
|
schema_updated, std::move(defaults),
|
||||||
|
std::move(constructors));
|
||||||
|
ObjectType object = create_object<T, RealmClass<T>>(protected_ctx, new SharedRealm(final_realm));
|
||||||
|
|
||||||
|
ValueType callback_arguments[2];
|
||||||
|
callback_arguments[0] = object;
|
||||||
|
callback_arguments[1] = Value::from_null(protected_ctx);
|
||||||
|
Function<T>::callback(protected_ctx, protected_callback, typename T::Object(), 2, callback_arguments);
|
||||||
|
|
||||||
|
// Ensure the sync session is kept alive while we close and reopen the Realm
|
||||||
|
static_cast<void>(session);
|
||||||
|
});
|
||||||
|
session->wait_for_download_completion(std::move(wait_handler));
|
||||||
|
return_value.set(create_object<T, SessionClass<T>>(ctx, new WeakSession(session)));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -118,14 +118,12 @@ const getPartialRealm = () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return Realm.open(config); // Creates the Realm on the server
|
return Realm.open(config); // Creates the Realm on the server
|
||||||
}).then(realm => {
|
})
|
||||||
return waitForUpload(realm);
|
.then(waitForUpload)
|
||||||
}).then(realm => {
|
.then(waitForDownload)
|
||||||
return waitForDownload(realm);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const assertFullAccess= function(permission) {
|
const assertFullAccess = function(permission) {
|
||||||
TestCase.assertTrue(permission.canCreate);
|
TestCase.assertTrue(permission.canCreate);
|
||||||
TestCase.assertTrue(permission.canRead);
|
TestCase.assertTrue(permission.canRead);
|
||||||
TestCase.assertTrue(permission.canUpdate);
|
TestCase.assertTrue(permission.canUpdate);
|
||||||
|
@ -276,46 +274,44 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
testAddPermissionSchemaForQueryBasedRealmOnly() {
|
testAddPermissionSchemaForQueryBasedRealmOnly() {
|
||||||
return new Promise((resolve, reject) => {
|
return Realm.Sync.User.register('http://localhost:9080', uuid(), 'password').then((user) => {
|
||||||
Realm.Sync.User.register('http://localhost:9080', uuid(), 'password').then((user) => {
|
let config = {
|
||||||
let config = {
|
schema: [],
|
||||||
sync: {
|
sync: {
|
||||||
user: user,
|
user: user,
|
||||||
url: `realm://NO_SERVER/foo`,
|
url: `realm://NO_SERVER/foo`,
|
||||||
fullSynchronization: false,
|
fullSynchronization: false,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let realm = new Realm(config);
|
let realm = new Realm(config);
|
||||||
TestCase.assertTrue(realm.empty);
|
TestCase.assertTrue(realm.empty);
|
||||||
|
|
||||||
TestCase.assertEqual(realm.schema.length, 5 + 1); // 5 = see below, 1 = __ResultSets
|
TestCase.assertEqual(realm.schema.length, 5 + 1); // 5 = see below, 1 = __ResultSets
|
||||||
TestCase.assertEqual(realm.schema.filter(schema => schema.name === '__Class').length, 1);
|
TestCase.assertEqual(realm.schema.filter(schema => schema.name === '__Class').length, 1);
|
||||||
TestCase.assertEqual(realm.schema.filter(schema => schema.name === '__Permission').length, 1);
|
TestCase.assertEqual(realm.schema.filter(schema => schema.name === '__Permission').length, 1);
|
||||||
TestCase.assertEqual(realm.schema.filter(schema => schema.name === '__Realm').length, 1);
|
TestCase.assertEqual(realm.schema.filter(schema => schema.name === '__Realm').length, 1);
|
||||||
TestCase.assertEqual(realm.schema.filter(schema => schema.name === '__Role').length, 1);
|
TestCase.assertEqual(realm.schema.filter(schema => schema.name === '__Role').length, 1);
|
||||||
TestCase.assertEqual(realm.schema.filter(schema => schema.name === '__User').length, 1);
|
TestCase.assertEqual(realm.schema.filter(schema => schema.name === '__User').length, 1);
|
||||||
|
|
||||||
realm.close();
|
realm.close();
|
||||||
Realm.deleteFile(config);
|
Realm.deleteFile(config);
|
||||||
|
|
||||||
// Full sync shouldn't include the permission schema
|
// Full sync shouldn't include the permission schema
|
||||||
config = {
|
config = {
|
||||||
sync: {
|
schema: [],
|
||||||
user: user,
|
sync: {
|
||||||
url: `realm://NO_SERVER/foo`,
|
user: user,
|
||||||
fullSynchronization: true
|
url: `realm://NO_SERVER/foo`,
|
||||||
}
|
fullSynchronization: true
|
||||||
};
|
}
|
||||||
realm = new Realm(config);
|
};
|
||||||
TestCase.assertTrue(realm.empty);
|
realm = new Realm(config);
|
||||||
TestCase.assertEqual(realm.schema.length, 0);
|
TestCase.assertTrue(realm.empty);
|
||||||
|
TestCase.assertEqual(realm.schema.length, 0);
|
||||||
|
|
||||||
realm.close();
|
realm.close();
|
||||||
Realm.deleteFile(config);
|
Realm.deleteFile(config);
|
||||||
|
|
||||||
resolve();
|
|
||||||
}).catch(error => reject(error));
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -353,23 +349,22 @@ module.exports = {
|
||||||
realm.close();
|
realm.close();
|
||||||
Realm.deleteFile(config);
|
Realm.deleteFile(config);
|
||||||
// connecting with an empty schema should be possible, permission is added implicitly
|
// connecting with an empty schema should be possible, permission is added implicitly
|
||||||
Realm.open(user.createConfiguration()).then((realm) => {
|
return Realm.open(user.createConfiguration());
|
||||||
return waitForUpload(realm);
|
})
|
||||||
}).then((realm) => {
|
.then(waitForUpload)
|
||||||
return waitForDownload(realm);
|
.then(waitForDownload)
|
||||||
}).then((realm) => {
|
.then((realm) => {
|
||||||
let permissions = realm.objects(Realm.Permissions.Permission).filtered(`role.name = '__User:${user.identity}'`);
|
let permissions = realm.objects(Realm.Permissions.Permission).filtered(`role.name = '__User:${user.identity}'`);
|
||||||
TestCase.assertEqual(permissions.length, 1);
|
TestCase.assertEqual(permissions.length, 1);
|
||||||
TestCase.assertTrue(permissions[0].canRead);
|
TestCase.assertTrue(permissions[0].canRead);
|
||||||
TestCase.assertTrue(permissions[0].canQuery);
|
TestCase.assertTrue(permissions[0].canQuery);
|
||||||
TestCase.assertTrue(permissions[0].canUpdate);
|
TestCase.assertTrue(permissions[0].canUpdate);
|
||||||
TestCase.assertFalse(permissions[0].canDelete);
|
TestCase.assertFalse(permissions[0].canDelete);
|
||||||
TestCase.assertFalse(permissions[0].canSetPermissions);
|
TestCase.assertFalse(permissions[0].canSetPermissions);
|
||||||
TestCase.assertFalse(permissions[0].canCreate);
|
TestCase.assertFalse(permissions[0].canCreate);
|
||||||
TestCase.assertFalse(permissions[0].canModifySchema);
|
TestCase.assertFalse(permissions[0].canModifySchema);
|
||||||
realm.close();
|
realm.close();
|
||||||
resolve();
|
resolve();
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -379,127 +374,106 @@ module.exports = {
|
||||||
|
|
||||||
testFindOrCreate_realmPermissions() {
|
testFindOrCreate_realmPermissions() {
|
||||||
return getPartialRealm().then(realm => {
|
return getPartialRealm().then(realm => {
|
||||||
return new Promise((resolve, reject) => {
|
let realmPermissions = realm.permissions();
|
||||||
let realmPermissions = realm.permissions();
|
TestCase.assertEqual(2, realm.objects('__Role').length); // [ "everyone", "__User:<xxx>" ]
|
||||||
TestCase.assertEqual(2, realm.objects('__Role').length); // [ "everyone", "__User:<xxx>" ]
|
realm.write(() => {
|
||||||
realm.write(() => {
|
let permissions = realmPermissions.findOrCreate("foo");
|
||||||
let permissions = realmPermissions.findOrCreate("foo");
|
TestCase.assertEqual("foo", permissions.role.name);
|
||||||
TestCase.assertEqual("foo", permissions.role.name);
|
TestCase.assertEqual(0, permissions.role.members.length);
|
||||||
TestCase.assertEqual(0, permissions.role.members.length);
|
TestCase.assertFalse(permissions.canCreate);
|
||||||
TestCase.assertFalse(permissions.canCreate);
|
TestCase.assertFalse(permissions.canRead);
|
||||||
TestCase.assertFalse(permissions.canRead);
|
TestCase.assertFalse(permissions.canUpdate);
|
||||||
TestCase.assertFalse(permissions.canUpdate);
|
TestCase.assertFalse(permissions.canDelete);
|
||||||
TestCase.assertFalse(permissions.canDelete);
|
TestCase.assertFalse(permissions.canQuery);
|
||||||
TestCase.assertFalse(permissions.canQuery);
|
TestCase.assertFalse(permissions.canModifySchema);
|
||||||
TestCase.assertFalse(permissions.canModifySchema);
|
TestCase.assertFalse(permissions.canSetPermissions);
|
||||||
TestCase.assertFalse(permissions.canSetPermissions);
|
TestCase.assertEqual(3, realm.objects('__Role').length); // [ "everyone", "__User:<xxx>", "foo" ]
|
||||||
TestCase.assertEqual(3, realm.objects('__Role').length); // [ "everyone", "__User:<xxx>", "foo" ]
|
|
||||||
});
|
|
||||||
resolve();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
testFindOrCreate_existingRole() {
|
testFindOrCreate_existingRole() {
|
||||||
return getPartialRealm().then(realm => {
|
return getPartialRealm().then(realm => {
|
||||||
return new Promise((resolve, reject) => {
|
realm.write(() => {
|
||||||
realm.write(() => {
|
realm.create('__Role', {'name':'foo'});
|
||||||
realm.create('__Role', {'name':'foo'});
|
});
|
||||||
});
|
TestCase.assertEqual(3, realm.objects('__Role').length); // [ "everyone", "__User:xxx", "foo" ]
|
||||||
TestCase.assertEqual(3, realm.objects('__Role').length); // [ "everyone", "__User:xxx", "foo" ]
|
|
||||||
|
|
||||||
let realmPermissions = realm.permissions();
|
let realmPermissions = realm.permissions();
|
||||||
realm.write(() => {
|
realm.write(() => {
|
||||||
let permissions = realmPermissions.findOrCreate("foo");
|
let permissions = realmPermissions.findOrCreate("foo");
|
||||||
TestCase.assertEqual("foo", permissions.role.name);
|
TestCase.assertEqual("foo", permissions.role.name);
|
||||||
TestCase.assertFalse(permissions.canCreate);
|
TestCase.assertFalse(permissions.canCreate);
|
||||||
TestCase.assertFalse(permissions.canRead);
|
TestCase.assertFalse(permissions.canRead);
|
||||||
TestCase.assertFalse(permissions.canUpdate);
|
TestCase.assertFalse(permissions.canUpdate);
|
||||||
TestCase.assertFalse(permissions.canDelete);
|
TestCase.assertFalse(permissions.canDelete);
|
||||||
TestCase.assertFalse(permissions.canQuery);
|
TestCase.assertFalse(permissions.canQuery);
|
||||||
TestCase.assertFalse(permissions.canModifySchema);
|
TestCase.assertFalse(permissions.canModifySchema);
|
||||||
TestCase.assertFalse(permissions.canSetPermissions);
|
TestCase.assertFalse(permissions.canSetPermissions);
|
||||||
TestCase.assertEqual(3, realm.objects('__Role').length); // [ "everyone", "__User:xxx", "foo" ]
|
TestCase.assertEqual(3, realm.objects('__Role').length); // [ "everyone", "__User:xxx", "foo" ]
|
||||||
});
|
|
||||||
resolve();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
testFindOrCreate_classPermissions() {
|
testFindOrCreate_classPermissions() {
|
||||||
return getPartialRealm().then(realm => {
|
return getPartialRealm().then(realm => {
|
||||||
return new Promise((resolve, reject) => {
|
let classPermissions = realm.permissions('__Class');
|
||||||
let classPermissions = realm.permissions('__Class');
|
TestCase.assertEqual(2, realm.objects('__Role').length); // [ "everyone", "__User:xxx" ]
|
||||||
TestCase.assertEqual(2, realm.objects('__Role').length); // [ "everyone", "__User:xxx" ]
|
realm.write(() => {
|
||||||
realm.write(() => {
|
let permissions = classPermissions.findOrCreate("foo");
|
||||||
let permissions = classPermissions.findOrCreate("foo");
|
TestCase.assertEqual("foo", permissions.role.name);
|
||||||
TestCase.assertEqual("foo", permissions.role.name);
|
TestCase.assertEqual(0, permissions.role.members.length);
|
||||||
TestCase.assertEqual(0, permissions.role.members.length);
|
TestCase.assertFalse(permissions.canCreate);
|
||||||
TestCase.assertFalse(permissions.canCreate);
|
TestCase.assertFalse(permissions.canRead);
|
||||||
TestCase.assertFalse(permissions.canRead);
|
TestCase.assertFalse(permissions.canUpdate);
|
||||||
TestCase.assertFalse(permissions.canUpdate);
|
TestCase.assertFalse(permissions.canDelete);
|
||||||
TestCase.assertFalse(permissions.canDelete);
|
TestCase.assertFalse(permissions.canQuery);
|
||||||
TestCase.assertFalse(permissions.canQuery);
|
TestCase.assertFalse(permissions.canModifySchema);
|
||||||
TestCase.assertFalse(permissions.canModifySchema);
|
TestCase.assertFalse(permissions.canSetPermissions);
|
||||||
TestCase.assertFalse(permissions.canSetPermissions);
|
TestCase.assertEqual(3, realm.objects('__Role').length); // [ "everyone", "__User:xxx", "foo" ]
|
||||||
TestCase.assertEqual(3, realm.objects('__Role').length); // [ "everyone", "__User:xxx", "foo" ]
|
|
||||||
});
|
|
||||||
resolve();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
testFindOrCreate_throwsOutsideWrite() {
|
testFindOrCreate_throwsOutsideWrite() {
|
||||||
return getPartialRealm().then(realm => {
|
return getPartialRealm().then(realm => {
|
||||||
return new Promise((resolve, reject) => {
|
let realmPermissions = realm.permissions();
|
||||||
let realmPermissions = realm.permissions();
|
TestCase.assertThrows(() => realmPermissions.findOrCreate("foo"));
|
||||||
TestCase.assertThrows(() => realmPermissions.findOrCreate("foo"));
|
let classPermissions = realm.permissions('__Class');
|
||||||
let classPermissions = realm.permissions('__Class');
|
TestCase.assertThrows(() => classPermissions.findOrCreate("foo"));
|
||||||
TestCase.assertThrows(() => classPermissions.findOrCreate("foo"));
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
testPermissions_Realm: function() {
|
testPermissions_Realm: function() {
|
||||||
return getPartialRealm().then(realm => {
|
return getPartialRealm().then(realm => {
|
||||||
return new Promise((resolve, reject) => {
|
let permissions = realm.permissions();
|
||||||
let permissions = realm.permissions();
|
TestCase.assertEqual(1, permissions.permissions.length);
|
||||||
TestCase.assertEqual(1, permissions.permissions.length);
|
let perm = permissions.permissions[0];
|
||||||
let perm = permissions.permissions[0];
|
TestCase.assertEqual("everyone", perm.role.name);
|
||||||
TestCase.assertEqual("everyone", perm.role.name);
|
assertFullAccess(perm);
|
||||||
assertFullAccess(perm);
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
testPermissions_Class: function() {
|
testPermissions_Class: function() {
|
||||||
return getPartialRealm().then(realm => {
|
return getPartialRealm().then(realm => {
|
||||||
return new Promise((resolve, reject) => {
|
let permissions = realm.permissions('__Class');
|
||||||
let permissions = realm.permissions('__Class');
|
TestCase.assertEqual('__Class', permissions.name)
|
||||||
TestCase.assertEqual('__Class', permissions.name)
|
TestCase.assertEqual(1, permissions.permissions.length);
|
||||||
TestCase.assertEqual(1, permissions.permissions.length);
|
let perm = permissions.permissions[0];
|
||||||
let perm = permissions.permissions[0];
|
TestCase.assertEqual("everyone", perm.role.name);
|
||||||
TestCase.assertEqual("everyone", perm.role.name);
|
TestCase.assertTrue(perm.canCreate);
|
||||||
TestCase.assertTrue(perm.canCreate);
|
TestCase.assertTrue(perm.canRead);
|
||||||
TestCase.assertTrue(perm.canRead);
|
TestCase.assertTrue(perm.canUpdate);
|
||||||
TestCase.assertTrue(perm.canUpdate);
|
TestCase.assertFalse(perm.canDelete);
|
||||||
TestCase.assertFalse(perm.canDelete);
|
TestCase.assertTrue(perm.canQuery);
|
||||||
TestCase.assertTrue(perm.canQuery);
|
TestCase.assertFalse(perm.canModifySchema);
|
||||||
TestCase.assertFalse(perm.canModifySchema);
|
TestCase.assertTrue(perm.canSetPermissions);
|
||||||
TestCase.assertTrue(perm.canSetPermissions);
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
testPermissions_Class_InvalidClassArgument: function() {
|
testPermissions_Class_InvalidClassArgument: function() {
|
||||||
return getPartialRealm().then(realm => {
|
return getPartialRealm().then(realm => {
|
||||||
return new Promise((resolve, reject) => {
|
TestCase.assertThrows(() => realm.permissions('foo'));
|
||||||
TestCase.assertThrows(() => realm.permissions('foo'));
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue