Skip to content

[#394] Resolve "signalc: send a message from a number registered in a previous session (persistence)"

aguestuser requested to merge 394-sc-persist-keystore-across-sessions into main

Closes #394 (closed)

context

  • we are building signalc up piece-by-piece
  • last MR (!496 (merged) resolving #393 (closed)) we build a CLI toy that could use signalc to register and verify a signal account and send messages from it, but since this toy held all the state about the account in memory, it would have to re-register and re-verify after stopping and starting again
  • this MR adds a postgresql persistence layer and (1) moves the singal protocol store logic that had been in-memory to disk storage, (2) adds a new store for account data and stores it to disk as well
  • as a result: we can now use the same phone number in our toy demo across several sessions without having to re-register. yay! :)

changes

persistence layer

  • add sql representation for protocol store & account state
  • abstract both as "stores" (ie: use a repository pattern that encapsulates the storage implementation. makes it easy to swap in an in-memory store or use the store as a mocking boundary in tests)

configuration

  • create Application module that constructs itself from an injected set of Configs
  • Config.App objects are built out of nested data classes which can cascade on top of one another to override defaults and inject dependencies at runtime
  • Application objects can be constructed in the boot sequence or before running tests to generate different configurations for different settings
  • provide test/dev/prod defaults as properties on Config to cver the most common settings
  • settings of note:
    • reads db url and uses it to construct account & protocol stores which it exposes as a field on app
    • sets signal service config strings (which could later be read from env vars) and exposes the signalConfig object needed to construct signal-aware classes
    • leaves seam to modify what kind of stores will be used for Accounts and Protocol persistence layer
  • abandon an attempt to read cascading configs from yaml, but leave breadcrumbs here: #410 logic layer

logic layer

  • pass Appplication as first arg to every class in logic layer
  • refactor AccountManager to work w/ AccountStore & ProtocolStore (as injected as fields on App)
    • store state transtions from new -> registered -> verified in store
    • retrieve accounts and their state across sessions (via store#findOrCreate)
    • add ability to reason about multiple accounts to the protocol store layer (which means making one protocol store for each account)
    • move SignalServiceAccountManager objects (which we use to update signal server about account state via http calls) from lazy field on account data classes to memoized hash map encapsulated by AccountManager (reason: these objects now are stateful as they require a reference to app to know about the protocol store, so they can't be constructed as stateless properties of a data class)
  • refactor Messenger -> MessageSender to work w/ stores on App
    • as with AccountManager, only instantiate one supervising object per app instanste, maintain a memoized collection of accounts
    • include sender as argument to #send and enforce that only a verified account may send a message
    • require that recipient be an address (not a string), and provide helpers for constructing an address from either a phone number or a UUID
  • note: both of these should work well as actor-like classes that consume messages over channels as a concurrency strategy in the near future

tooling

  • migrations:
    • adopt liquibase as migration framework
    • add liquibase gradle task to run migrations
    • add gitlab tooling to use liquibase to run migrations in CI
  • forward localhost:5432 to the postgres docker container so we can:
    • run db-touching tests locally without modifying configs
    • do the above without having to run migrations against a non-dockerized version of the db
  • util classes
    • provide MigrationUtil (which can generate SQL statements to be used in liquibase migrations from exposed classes)
    • provide InMemoryAccountStore and InMemoryProtocolStore implementations for possible future use as testing aids
    • start to add custom matchers (to assert things about message sending)
  • spruce up make for signalc (give it all the commands it needs to run, stop, test, migrate, etc.)
  • bake the signal truststore into the docker image (so we don't need to mount it and so we can have it in CI)

demo

  • update Main.kt to user new persistence layer so it can look up pre-existing accounts and send messages to them (without having to register them again)
Edited by aguestuser

Merge request reports