User Authentication and Scope/Collection Configuration for "Offline First" .net App with Replicator & Capella

Hi all,
I started with the .net maui cbl realm example app and quickly worked towards refactoring that data service into standalone, dependency-injection friendly project so I could test with a “CBL repository service” using xunit and then bring it back over to a .net maui application and/or wherever.

I am working from an “offline first” pattern, creating a local CouchbaseLite database and only (when I get it working :blush:) synchronizing to a Capella hosted app service when needed/requested.

The local version of the db is running and passing basic CRUD tests, but I’m stuck implementing the replication (‘synchronization’) piece.

I have a Capella app with an endpoint named lf and a bucket named lf, that has a scope named data with a collection in it called tasks.

These screens are probably more descriptive:

Failing Replicator Setup
Finally, I have an “AuthenticateAsync” method (see below) in my “UserRepostory” class where I am trying (and failing) to get the Capella replicator working set up so that my local db will sync to the remote Capella hosted instance. The code in AuthenticateAsync fails at await completionSource.Task.WaitAsync(_options.ConnectionTimeout); with “{“Authentication error: CouchbaseLiteException (WebSocketDomain / 404): Collection ‘_default’ is not found on the remote server.”}”

But I’m not trying to connect to _default collection :frowning: I’d appreciate any guidance, including examples of configuring Replicator for .net when the database is presumed local and “offline first”. Am I breaking naming convetions?

You can see all the logging as I tried to work my through other errors. The code makes me think I should be able to “see” tasks collection, but something about my setup or code is making it expect or route to “_default” collection. (?)

Migrating (quickly) from Mong$#. Glad to be on CBL and Capella, but very new.
Thanks,
Dave G

public async Task<User> AuthenticateAsync(string username, string password)
{
    if (string.IsNullOrEmpty(username))
        throw new ArgumentException("Username cannot be null or empty", nameof(username));
    if (string.IsNullOrEmpty(password))
        throw new ArgumentException("Password cannot be null or empty", nameof(password));

    _logger.LogInformation($"Starting authentication for user: {username}");

    try
    {
        if (_database == null)
        {
            throw new InvalidOperationException("Database not initialized");
        }

        _logger.LogInformation("Accessing collection 'tasks' in scope 'data'...");
        var collection = _database.GetCollection("tasks", "data");
        if (collection == null)
        {
            _logger.LogError("Could not access collection 'tasks' in scope 'data'");
            throw new InvalidOperationException("Required collection not found");
        }
        _logger.LogInformation($"Got collection: Name={collection.Name}, Scope={collection.Scope.Name}");

        var authenticator = new BasicAuthenticator(username, password);
        var targetEndpoint = new URLEndpoint(new Uri(_options.EndpointUrl));

        _logger.LogInformation("Creating replicator configuration");

        // Create specific collection-to-collection mapping
        var collectionConfig = new CollectionConfiguration();
        var collections = new Dictionary<string, CollectionConfiguration>
    {
        { "data.tasks", collectionConfig }  // Explicitly map to data.tasks
    };

        var config = new ReplicatorConfiguration(_database, targetEndpoint)
        {
            Authenticator = authenticator,
            ReplicatorType = ReplicatorType.PushAndPull,
            Continuous = false
        };

        // Add our specific collection
        config.AddCollection(collection, collectionConfig);

        _logger.LogInformation("Created replicator configuration with explicit collection mapping");

        using var testReplicator = new Replicator(config);
        var completionSource = new TaskCompletionSource<bool>();

        testReplicator.AddChangeListener((sender, args) =>
        {
            if (args.Status.Error != null)
            {
                var errorMsg = $"Authentication error: {args.Status.Error.Message}";
                _logger.LogError(errorMsg);
                _logger.LogError($"Detailed status: Activity={args.Status.Activity}, Progress={args.Status.Progress}");
                completionSource.TrySetException(new UnauthorizedAccessException(errorMsg));
            }
            else if (args.Status.Activity == ReplicatorActivityLevel.Stopped)
            {
                _logger.LogInformation("Authentication successful");
                completionSource.TrySetResult(true);
            }
            else
            {
                _logger.LogInformation($"Replicator status: Activity={args.Status.Activity}, Progress={args.Status.Progress}");
            }
        });

        _logger.LogInformation("Starting replicator");
        testReplicator.Start();

        await completionSource.Task.WaitAsync(_options.ConnectionTimeout);

        _currentUser = new User(username, password);
        return _currentUser;
    }
    catch (Exception ex) when (ex is not UnauthorizedAccessException)
    {
        _logger.LogError(ex, "Authentication failed");
        throw new InvalidOperationException("Authentication service unavailable", ex);
    }
}

Also, fwiw, I’d welcome links to any examples for replicator with .net and cbl and am happy to restart the Capella app from scratch if that’s what it takes. Real goal is to get an “offline first” plus capella sync working.

It seems like the Replicator is trying to sync with the default collection for some reason. Can you create ReplicatorConfiguration without the database as the following to see if that helps?

var config = new ReplicatorConfiguration(targetEndpoint)
{

}

Actually ReplicatorConfiguration(_database, targetEndpoint) is deprecated and it’s for configuring the replicator to sync with the default collection.

Please change the code to what I suggested on the previous reply. That should work.

Bravo and thanks for the help! That fixed it. Much appreciated!