Skip to content

Commit

Permalink
Add change listener for app and user
Browse files Browse the repository at this point in the history
Add a member variable to store listener tokens to app and user.
Store the shared pointer to app and user as a member variable on
User and App classes to ensure that the tokens are moved correctly
and not copied.
Write tests for app listener.
  • Loading branch information
takameyer committed Apr 29, 2022
1 parent dda706b commit f0815d5
Show file tree
Hide file tree
Showing 9 changed files with 207 additions and 58 deletions.
8 changes: 5 additions & 3 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,29 +42,31 @@
"type": "node",
"request": "launch",
"name": "Debug Node Unit Tests (rebuild)",
"preLaunchTask": "rebuild-node-tests",
"preLaunchTask": "Build Node Tests",
"cwd": "${workspaceRoot}/tests",
"console": "integratedTerminal",
"program": "${workspaceRoot}/tests/node_modules/jasmine/bin/jasmine.js",
"runtimeArgs": [
"--expose_gc"
],
"args": [
"spec/unit_tests.js",
"--filter=."
"--filter=${input:testFilter}"
]
},
{
"type": "node",
"request": "launch",
"name": "Debug Node Unit Tests",
"cwd": "${workspaceRoot}/tests",
"console": "integratedTerminal",
"program": "${workspaceRoot}/tests/node_modules/jasmine/bin/jasmine.js",
"runtimeArgs": [
"--expose_gc"
],
"args": [
"spec/unit_tests.js",
"--filter=."
"--filter=${input:testFilter}"
]
},
{
Expand Down
96 changes: 82 additions & 14 deletions src/js_app.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,34 @@
#include "js_network_transport.hpp"
#include "js_email_password_auth.hpp"

#include <forward_list>

using SharedApp = std::shared_ptr<realm::app::App>;
using SharedUser = std::shared_ptr<realm::SyncUser>;
using AppToken = realm::Subscribable<realm::app::App>::Token;

namespace realm {
namespace js {

template <typename T>
class AppClass : public ClassDefinition<T, SharedApp> {
class App {
public:
App(const SharedApp& l)
: m_app(l)
{
}

App(const App&) = delete;
App& operator=(const App&) = delete;

using CallbackTokenPair = std::pair<Protected<typename T::Function>, AppToken>;
std::forward_list<CallbackTokenPair> m_notification_tokens;

SharedApp m_app;
};

template <typename T>
class AppClass : public ClassDefinition<T, realm::js::App<T>> {
using ContextType = typename T::Context;
using FunctionType = typename T::Function;
using ObjectType = typename T::Object;
Expand All @@ -51,10 +71,12 @@ class AppClass : public ClassDefinition<T, SharedApp> {
using Arguments = js::Arguments<T>;
using NetworkTransport = JavaScriptNetworkTransport<T>;
using NetworkTransportFactory = typename NetworkTransport::NetworkTransportFactory;
using Token = Subscribable<SharedApp>::Token;

public:
const std::string name = "App";


/**
* Generates instances of GenericNetworkTransport, eventually allowing Realm Object Store to perform network
* requests. Exposed to allow other components (ex the RPCServer) to override the underlying implementation.
Expand Down Expand Up @@ -99,12 +121,17 @@ class AppClass : public ClassDefinition<T, SharedApp> {
static void clear_app_cache(ContextType, ObjectType, Arguments&, ReturnValue&);
static void get_app(ContextType, ObjectType, Arguments&, ReturnValue&);
static void set_versions(ContextType, ObjectType, Arguments&, ReturnValue&);
static void add_listener(ContextType, ObjectType, Arguments&, ReturnValue&);
static void remove_listener(ContextType, ObjectType, Arguments&, ReturnValue&);


MethodMap<T> const methods = {
{"_logIn", wrap<log_in>},
{"switchUser", wrap<switch_user>},
{"_removeUser", wrap<remove_user>},
{"_deleteUser", wrap<delete_user>},
{"addListener", wrap<add_listener>},
{"removeListener", wrap<remove_listener>},
};

MethodMap<T> const static_methods = {
Expand All @@ -120,7 +147,7 @@ inline typename T::Function AppClass<T>::create_constructor(ContextType ctx)
template <typename T>
inline typename T::Object AppClass<T>::create_instance(ContextType ctx, SharedApp app)
{
return create_object<T, AppClass<T>>(ctx, new SharedApp(app));
return create_object<T, AppClass<T>>(ctx, new realm::js::App<T>(app));
}

template <typename T>
Expand Down Expand Up @@ -204,7 +231,7 @@ void AppClass<T>::constructor(ContextType ctx, ObjectType this_object, Arguments

SharedApp app = app::App::get_shared_app(config, client_config);

set_internal<T, AppClass<T>>(ctx, this_object, new SharedApp(app));
set_internal<T, AppClass<T>>(ctx, this_object, new realm::js::App<T>(app));
}

template <typename T>
Expand All @@ -217,7 +244,7 @@ std::string AppClass<T>::get_user_agent()
template <typename T>
void AppClass<T>::get_app_id(ContextType ctx, ObjectType this_object, ReturnValue& return_value)
{
auto app = *get_internal<T, AppClass<T>>(ctx, this_object);
auto app = get_internal<T, AppClass<T>>(ctx, this_object)->m_app;
return_value.set(Value::from_string(ctx, app->config().app_id));
}

Expand All @@ -226,7 +253,7 @@ void AppClass<T>::log_in(ContextType ctx, ObjectType this_object, Arguments& arg
{
args.validate_maximum(2);

auto app = *get_internal<T, AppClass<T>>(ctx, this_object);
auto app = get_internal<T, AppClass<T>>(ctx, this_object)->m_app;

auto credentials_object = Value::validated_to_object(ctx, args[0]);
auto callback_function = Value::validated_to_function(ctx, args[1]);
Expand All @@ -244,7 +271,7 @@ void AppClass<T>::log_in(ContextType ctx, ObjectType this_object, Arguments& arg
template <typename T>
void AppClass<T>::get_all_users(ContextType ctx, ObjectType this_object, ReturnValue& return_value)
{
auto app = *get_internal<T, AppClass<T>>(ctx, this_object);
auto app = get_internal<T, AppClass<T>>(ctx, this_object)->m_app;

auto users = Object::create_empty(ctx);
for (auto user : app->all_users()) {
Expand All @@ -259,7 +286,7 @@ void AppClass<T>::get_all_users(ContextType ctx, ObjectType this_object, ReturnV
template <typename T>
void AppClass<T>::get_current_user(ContextType ctx, ObjectType this_object, ReturnValue& return_value)
{
auto app = *get_internal<T, AppClass<T>>(ctx, this_object);
auto app = get_internal<T, AppClass<T>>(ctx, this_object)->m_app;
auto user = app->current_user();
if (user) {
return_value.set(create_object<T, UserClass<T>>(ctx, new User<T>(std::move(user), std::move(app))));
Expand All @@ -274,10 +301,10 @@ void AppClass<T>::switch_user(ContextType ctx, ObjectType this_object, Arguments
{
args.validate_count(1);

auto app = *get_internal<T, AppClass<T>>(ctx, this_object);
auto app = get_internal<T, AppClass<T>>(ctx, this_object)->m_app;
auto user = get_internal<T, UserClass<T>>(ctx, Value::validated_to_object(ctx, args[0], "user"));

app->switch_user(*user);
app->switch_user(user->m_user);
return_value.set(Value::from_undefined(ctx));
}

Expand All @@ -286,11 +313,11 @@ void AppClass<T>::remove_user(ContextType ctx, ObjectType this_object, Arguments
{
args.validate_count(2);

auto app = *get_internal<T, AppClass<T>>(ctx, this_object);
auto app = get_internal<T, AppClass<T>>(ctx, this_object)->m_app;
auto user = get_internal<T, UserClass<T>>(ctx, Value::validated_to_object(ctx, args[0], "user"));
auto callback = Value::validated_to_function(ctx, args[1], "callback");

app->remove_user(*user, Function::wrap_void_callback(ctx, this_object, callback));
app->remove_user(user->m_user, Function::wrap_void_callback(ctx, this_object, callback));
}

/**
Expand All @@ -309,18 +336,18 @@ void AppClass<T>::delete_user(ContextType ctx, ObjectType this_object, Arguments
{
args.validate_count(2);

auto app = *get_internal<T, AppClass<T>>(ctx, this_object);
auto app = get_internal<T, AppClass<T>>(ctx, this_object)->m_app;
auto user = get_internal<T, UserClass<T>>(ctx, Value::validated_to_object(ctx, args[0], "user"));
auto callback = Value::validated_to_function(ctx, args[1], "callback");

app->delete_user(*user, Function::wrap_void_callback(ctx, this_object, callback));
app->delete_user(user->m_user, Function::wrap_void_callback(ctx, this_object, callback));
}


template <typename T>
void AppClass<T>::get_email_password_auth(ContextType ctx, ObjectType this_object, ReturnValue& return_value)
{
auto app = *get_internal<T, AppClass<T>>(ctx, this_object);
auto app = get_internal<T, AppClass<T>>(ctx, this_object)->m_app;
return_value.set(EmailPasswordAuthClass<T>::create_instance(ctx, app));
}

Expand Down Expand Up @@ -355,5 +382,46 @@ void AppClass<T>::set_versions(ContextType ctx, ObjectType this_object, Argument
AppClass<T>::platform_version = Object::validated_get_string(ctx, versions, "platformVersion");
}

template <typename T>
void AppClass<T>::add_listener(ContextType ctx, ObjectType this_object, Arguments& args, ReturnValue& return_value)
{
auto callback = Value::validated_to_function(ctx, args[0]);
auto app = get_internal<T, AppClass<T>>(ctx, this_object);
Protected<FunctionType> protected_callback(ctx, callback);
Protected<ObjectType> protected_this(ctx, this_object);
Protected<typename T::GlobalContext> protected_ctx(Context::get_global_context(ctx));

{
auto token = std::move(app->m_app->subscribe([=](const realm::app::App&) {
Function::callback(protected_ctx, protected_callback, 0, {});
}));

// Save token in a member vector of a function to token pair
app->m_notification_tokens.emplace_front(std::move(protected_callback), std::move(token));
}
}

template <typename T>
void AppClass<T>::remove_listener(ContextType ctx, ObjectType this_object, Arguments& args, ReturnValue& return_value)
{
auto callback = Value::validated_to_function(ctx, args[0]);
auto app = get_internal<T, AppClass<T>>(ctx, this_object);
Protected<FunctionType> protected_callback(ctx, callback);

auto& tokens = app->m_notification_tokens;
auto compare = [&](auto&& callback_token_pair) {
return typename Protected<FunctionType>::Comparator()(callback_token_pair.first, protected_callback);
};

// Retrieve the token with the given function and use to call unsubscribe
auto callback_token_pair_iter = std::find_if(tokens.begin(), tokens.end(), compare);

if (callback_token_pair_iter != tokens.end()) {
app->m_app->unsubscribe(callback_token_pair_iter->second);
tokens.remove_if(compare);
}
}


} // namespace js
} // namespace realm
2 changes: 1 addition & 1 deletion src/js_results.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ void ResultsClass<T>::add_listener(ContextType ctx, U& collection, ObjectType th
CollectionClass<T>::create_collection_change_set(protected_ctx, change_set)};
Function<T>::callback(protected_ctx, protected_callback, protected_this, 2, arguments);
});
collection.m_notification_tokens.emplace_back(protected_callback, std::move(token));
collection.m_notification_tokens.emplace_back(std::move(protected_callback), std::move(token));
}

template <typename T>
Expand Down
35 changes: 17 additions & 18 deletions src/js_sync.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -404,8 +404,7 @@ void UserClass<T>::session_for_on_disk_path(ContextType ctx, ObjectType this_obj
throw std::runtime_error("Invalid User instance. No internal instance is set");
}

auto user = internal->get();
// user.get();
auto user = internal->m_user;
if (auto session = user->session_for_on_disk_path(Value::validated_to_string(ctx, args[0]))) {
return_value.set(create_object<T, SessionClass<T>>(ctx, new WeakSession(session)));
}
Expand Down Expand Up @@ -849,9 +848,9 @@ void SyncClass<T>::get_sync_session(ContextType ctx, ObjectType this_object, Arg
auto partition_value_value = args[1];
std::string partition_value = partition_value_bson_to_string<T>(ctx, partition_value_value);

auto sync_config = SyncConfig(*user, partition_value);
auto sync_config = SyncConfig(user->m_user, partition_value);
auto path = user->m_app->sync_manager()->path_for_realm(sync_config);
if (auto session = (*user)->session_for_on_disk_path(path)) {
if (auto session = user->m_user->session_for_on_disk_path(path)) {
return_value.set(create_object<T, SessionClass<T>>(ctx, new WeakSession(session)));
return;
}
Expand All @@ -865,8 +864,8 @@ void SyncClass<T>::get_all_sync_sessions(ContextType ctx, ObjectType this_object
args.validate_count(1);

auto user_object = Value::validated_to_object(ctx, args[0], "user");
SharedUser user = *get_internal<T, UserClass<T>>(ctx, user_object);
auto all_sessions = user->all_sessions();
auto user = get_internal<T, UserClass<T>>(ctx, user_object);
auto all_sessions = user->m_user->all_sessions();
std::vector<ValueType> session_objects;
for (auto session : all_sessions) {
session_objects.push_back(create_object<T, SessionClass<T>>(ctx, new WeakSession(session)));
Expand All @@ -879,7 +878,7 @@ void SyncClass<T>::initiate_client_reset(ContextType ctx, ObjectType this_object
ReturnValue& return_value)
{
args.validate_count(2);
auto app = *get_internal<T, AppClass<T>>(ctx, Value::validated_to_object(ctx, args[0], "app"));
auto app = get_internal<T, AppClass<T>>(ctx, Value::validated_to_object(ctx, args[0], "app"))->m_app;
std::string path = Value::validated_to_string(ctx, args[1]);
if (!app->sync_manager()->immediately_run_file_actions(std::string(path))) {
throw std::runtime_error(
Expand All @@ -893,7 +892,7 @@ void SyncClass<T>::set_sync_log_level(ContextType ctx, ObjectType this_object, A
{
args.validate_count(2);

auto app = *get_internal<T, AppClass<T>>(ctx, Value::validated_to_object(ctx, args[0], "app"));
auto app = get_internal<T, AppClass<T>>(ctx, Value::validated_to_object(ctx, args[0], "app"))->m_app;
std::string log_level = Value::validated_to_string(ctx, args[1], "log level");

auto level = common::logger::Logger::get_level(log_level);
Expand All @@ -906,7 +905,7 @@ void SyncClass<T>::set_sync_logger(ContextType ctx, ObjectType this_object, Argu
{
args.validate_count(2);

auto app = *get_internal<T, AppClass<T>>(ctx, Value::validated_to_object(ctx, args[0], "app"));
auto app = get_internal<T, AppClass<T>>(ctx, Value::validated_to_object(ctx, args[0], "app"))->m_app;
auto callback_fn = Value::validated_to_function(ctx, args[1], "logger_callback");

Protected<typename T::GlobalContext> protected_ctx(Context<T>::get_global_context(ctx));
Expand All @@ -930,7 +929,7 @@ void SyncClass<T>::set_sync_user_agent(ContextType ctx, ObjectType this_object,
ReturnValue& return_value)
{
args.validate_count(2);
auto app = *get_internal<T, AppClass<T>>(ctx, Value::validated_to_object(ctx, args[0], "app"));
auto app = get_internal<T, AppClass<T>>(ctx, Value::validated_to_object(ctx, args[0], "app"))->m_app;
std::string application_user_agent = Value::validated_to_string(ctx, args[1], "user agent");
app->sync_manager()->set_user_agent(application_user_agent);
}
Expand All @@ -939,7 +938,7 @@ template <typename T>
void SyncClass<T>::reconnect(ContextType ctx, ObjectType this_object, Arguments& args, ReturnValue& return_value)
{
args.validate_count(1);
auto app = *get_internal<T, AppClass<T>>(ctx, Value::validated_to_object(ctx, args[0], "app"));
auto app = get_internal<T, AppClass<T>>(ctx, Value::validated_to_object(ctx, args[0], "app"))->m_app;
app->sync_manager()->reconnect();
}

Expand All @@ -948,7 +947,7 @@ void SyncClass<T>::has_existing_sessions(ContextType ctx, ObjectType this_object
ReturnValue& return_value)
{
args.validate_count(1);
auto app = *get_internal<T, AppClass<T>>(ctx, Value::validated_to_object(ctx, args[0], "app"));
auto app = get_internal<T, AppClass<T>>(ctx, Value::validated_to_object(ctx, args[0], "app"))->m_app;
return_value.set(app->sync_manager()->has_existing_sessions());
}

Expand Down Expand Up @@ -977,8 +976,8 @@ void SyncClass<T>::populate_sync_config(ContextType ctx, ObjectType realm_constr
if (!(Object::template is_instance<UserClass<T>>(ctx, user_object))) {
throw std::invalid_argument("Option 'user' is not a Realm.User object.");
}
SharedUser user = *get_internal<T, UserClass<T>>(ctx, user_object);
if (user->state() != SyncUser::State::LoggedIn) {
auto user = get_internal<T, UserClass<T>>(ctx, user_object);
if (user->m_user->state() != SyncUser::State::LoggedIn) {
throw std::runtime_error("User is no longer valid.");
}

Expand All @@ -988,13 +987,13 @@ void SyncClass<T>::populate_sync_config(ContextType ctx, ObjectType realm_constr
throw std::runtime_error("'partitionValue' cannot be specified when flexible sync is enabled");
}

config.sync_config = std::make_shared<SyncConfig>(user, SyncConfig::FLXSyncEnabled{});
config.sync_config = std::make_shared<SyncConfig>(user->m_user, SyncConfig::FLXSyncEnabled{});
}
else {
ValueType partition_value_value = Object::get_property(ctx, sync_config_object, "partitionValue");
std::string partition_value = partition_value_bson_to_string<T>(ctx, partition_value_value);

config.sync_config = std::make_shared<SyncConfig>(user, std::move(partition_value));
config.sync_config = std::make_shared<SyncConfig>(user->m_user, std::move(partition_value));
}

config.sync_config->error_handler = std::move(error_handler);
Expand Down Expand Up @@ -1130,7 +1129,7 @@ void SyncClass<T>::populate_sync_config(ContextType ctx, ObjectType realm_constr
}

config.schema_mode = SchemaMode::AdditiveExplicit;
config.path = user->sync_manager()->path_for_realm(*(config.sync_config));
config.path = user->m_user->sync_manager()->path_for_realm(*(config.sync_config));
}
}

Expand Down Expand Up @@ -1159,7 +1158,7 @@ void SyncClass<T>::enable_multiplexing(ContextType ctx, ObjectType this_object,
ReturnValue& return_value)
{
args.validate_count(1);
auto app = *get_internal<T, AppClass<T>>(ctx, Value::validated_to_object(ctx, args[0], "app"));
auto app = get_internal<T, AppClass<T>>(ctx, Value::validated_to_object(ctx, args[0], "app"))->m_app;
app->sync_manager()->enable_session_multiplexing();
}

Expand Down
Loading

0 comments on commit f0815d5

Please sign in to comment.