← back to specs

Booper: Technical spec

TODO

Overview

TODO: overview of components: mobile app, API, database, web dashboard

Mobile app:

  • Expo, React Native, TypeScript, (SQLite?)

API:

  • Elixir, Phoenix, Oban (workers), Hammer (rate limiter), API key auth for apps, JWT auth for dashboard

Data storage:

  • Redis (rate limiting), Postgres (db)

Web dashboard:

  • NextJS, Vercel, Tailwind,

API endpoints

TODO: API endpoints (public vs restricted vs admin dashboard)

Channels

  • GET /api/channels
    • Retrieve a list of subscribed channels by user
    • Retrieve a list of subscribed channels by app user, if the user has access to the app
    • Used in: mobile app
    • Request:
      • device_token (string, required) - the user device's push token
      • app_id (string, optional) - the ID of the app
    • Response:
      • channels - a list of channels
    • Errors:
      • 400 - missing device_token
      • If user does not have access to the app, or the device token is invalid, this will respond with an empty list of channels
  • POST /api/channels
    • Unlikely to be used, channels should just be auto-created when a user subscribes if it doesn't exist yet
    • Could be used in mobile app, dashboard
  • POST /api/channels/[name]/subscribe
    • POST /api/subscriptions
    • Subscribe a user to a channel by name or ID (only works for public channels?)
    • Request:
      • device_token (string, required) - the user device's push token
      • name (string) - the name of the public channel
      • channel_id (string|number) - the ID of the public channel
  • POST /api/channels/[name]/unsubscribe
    • DELETE /api/subscriptions
    • Unsubscribe a user to a channel by name or ID (only works for public channels?)
    • Request:
      • device_token (string, required) - the user device's push token
      • name (string) - the name of the public channel
      • channel_id (string|number) - the ID of the public channel
  • GET /api/channels/[name]/subscribers
    • GET /api/subscriptions
    • Retrieve list of subscribers by channel
    • Dashboard only

Notifications

  • GET /api/notifications
    • Retrieve notifications by channel
  • POST /api/notifications
    • Create a new notification for a given channel

Apps

  • GET /api/apps
    • List owned apps in dashboard
  • POST /api/apps
    • Create a new app
    • Used in: dashboard only
  • GET /api/apps/[id]
    • Retrieve app details
    • Used in: dashboard (app owner), mobile app (subscribed users)

Access codes

  • POST /api/access_codes
    • Generates a temporary access code for a private app
    • Request:
      • Authorization: Bearer {API key} - the API key for the app
      • channels[] - list of channel names or IDs that will be granted access
      • identifier (optional) - how the device should be identified (e.g. user ID, email, etc)
      • expires_at (optional) - expiration timestamp
    • Response:
      • access_code: the 6 digit access code
      • expires_at: the expiration timestamp (defaults to 5 minutes?)
  • POST /api/access_codes/[code]
    • Submits an access code
    • Request:
      • code - the access code
      • device_token - the device push token identifier
    • Response:
      • maybe respond with channels that are now accessible?

Public API

(Requires nothing.)

  • POST /api/notifications
  • POST /api/notify
    • Send a notification to a public channel
  • POST /api/devices
  • POST /api/identities
  • POST /api/identify
    • Register a device token

Admin API

(Requires app admin API key.)

  • POST /api/notifications
  • POST /api/notify
    • Send a push notification to your app
  • POST /api/access_codes
    • Generate an access code for your app

Mobile app API

(Requires device push token, or device unique identifier.)

  • GET /api/channels
    • List user channels
  • GET /api/channels/[id]
    • Retrieve channel details
  • GET /api/apps
    • List user apps
  • GET /api/apps/[id]
    • Retrieve app details
  • GET /api/notifications
    • List notifications by channel (or app channel)
  • POST /api/access_codes/[code]
    • Submit access code for app

Dashboard API

(Requires JWT.)

  • GET /api/apps
    • List admin apps
  • POST /api/apps
    • Create admin app
  • GET /api/apps/[id]
    • Retrieve app details
  • DELETE /api/apps/[id]
    • Delete app
  • GET /api/channels
    • List admin app channels
  • POST /api/channels
    • Create admin app channel
  • GET /api/channels/[id]
    • Retrieve app channel details
  • DELETE /api/channels/[id]
    • Delete app channel
  • GET /api/subscriptions
    • List admin app subscriptions
  • POST /api/subscriptions
    • Create admin app subscription
  • GET /api/subscriptions/[id]
    • Retrieve app subscription user details
  • DELETE /api/subscriptions/[id]
    • Delete app subscription

Components

TODO: what are the main technical components?

The lifecycle of a notification

TODO: talk about chunking, sending to worker, logging receipts, tracking successes/failures, retrying failures based on error message, etc

For public channel:

  • Hit /api/notify endpoint
  • Rate limit by IP or whatever identifier is available
  • 10 requests per second per IP
  • 10 requests per second per channel
  • Create message record in db
  • Enqueue message in Redis, or Genserver, or something that can handle heavy load if necessary
    • Maybe try this if Oban queue is full?
  • Enqueue message in Oban for push notification
  • Execute push notification worker for each notification
    • Worker should be idempotent
  • Send notification to push notification server (Expo, Apple, FCM)
  • Store receipts in db
  • Check receipts every 5-15 mins until success/failure case
  • If notification fails on first try
    • Check error message, log error, trigger alert if necessary
    • Retry if possible
  • If notification fails after checking receipt
    • Update receipt result in db with error
    • Check error message, log error, trigger alert if necessary
    • Retry if possible
  • If notification succeeds, clear from queue
    • Update receipt result in db with success
  • Partial success? How should that be handled?
  • Archive messages after 7 days
  • Hard delete messages after 30 days

For private app, same as above, except for...

  • 10 requests per second per app
  • 100 notifications per day
  • Pro plan:
    • 1000 notifications per day
    • archive messages after 30 days
    • hard delete after 100 days
  • Business plan:
    • priority queue
    • unlimited notifications
    • never archive/delete messages

Realtime updates

TODO: talk about polling vs websockets, dealing with app focus events, app backgrounded, tab switching not rerendering, etc

Offline mode

TODO: look into sqlite vs AsyncStorage, how to handle syncing local data with server (I guess this is easier when client is basically readonly), how local data is structured (sql? serialized by id?)