Omni
FeaturesCheck-inPricingFAQDocs
Sign inStart free
ArchitectureAll pages
Guide
  • Getting started
  • Checking in
  • Geofencing
  • Custom hours
  • Invites
  • Requests & approvals
  • Importing employees
  • Single sign-on
  • Roles & privacy
Product
  • Product spec
  • Roadmap
  • Saudi market benchmark
  • Architecture
Guide
  • Getting started
  • Checking in
  • Geofencing
  • Custom hours
  • Invites
  • Requests & approvals
  • Importing employees
  • Single sign-on
  • Roles & privacy
Product
  • Product spec
  • Roadmap
  • Saudi market benchmark
  • Architecture

Architecture

┌──────────────┐  ┌──────────────────┐  ┌───────────────────────────┐
│  apps/web    │  │  apps/android    │  │  apps/ios (SwiftUI)       │
│  Next.js     │  │  Expo / RN       │  │  Liquid Glass UI          │
│  React 19    │  │  + RN widget     │  │  + WidgetKit / Live Act.  │
└──────┬───────┘  └───────┬──────────┘  └────────────┬──────────────┘
       │ @omni/api, @omni/core, @omni/i18n           │ supabase-swift
       └──────────────┬─────────────┘                │
                      ▼                              ▼
             ┌──────────────────────────────────────────────┐
             │ Supabase                                     │
             │  Auth (email, OAuth, SAML SSO)               │
             │  Postgres + PostGIS + RLS (org isolation)    │
             │  RPC: clock, clock_status, create/redeem     │
             │  Realtime (DMs, presence, live attendance)   │
             │  Storage (documents, avatars, attachments)   │
             │  Edge Functions (push-dispatch, exports)     │
             └──────────────────────────────────────────────┘

Packages

Package Purpose
@omni/design-tokens The source of truth for the RSQ.sa brand. tokens.json is turned into CSS variables, a Tailwind v4 @theme, a React Native theme, and Swift OmniTheme.swift. It also ships the HT Moshreq Pro font files.
@omni/core Pure TypeScript domain logic: zod schemas, computeDay() (worked / late / overtime against a shift), the geofence check, EOSB, and invite-code generation. Used by web and Android; the logic is mirrored in SQL for server-side authority.
@omni/i18n Arabic and English dictionaries, plus dir() and number/date formatting with Hijri support.
@omni/api Supabase client factory, generated Database types, and typed wrappers for the RPCs.

Multi-tenancy & security

  • Every business table has an org_id. RLS policies use private.current_org(), private.is_member() and private.has_role(...) (SECURITY DEFINER helpers in a non-exposed schema), which read from org_members for the calling auth.uid().
    • The active org is stored in profiles.active_org_id. A user can belong to several orgs and switch between them.
  • Roles: owner > admin > hr > manager > employee. Managers see their reports through employees.manager_id.
  • Clients never insert attendance events directly. Only the SECURITY DEFINER RPC public.clock(...) writes them, after it has:
    • resolved the employee's effective policy and shift
    • checked the geofence (ST_DWithin on geography) and the GPS accuracy
    • de-duplicated by idempotency_key
    • set status to ok, flagged or rejected with a reason

Check-in from widgets

The widget can't open the app, so the token and the API call have to live outside the app's UI process.

iOS (SwiftPM + xtool, see apps/ios/README.md)

  • The app keeps the Supabase session in its own Keychain and writes the last omni_clock_status snapshot to the App Group group.sa.rsq.omni.
  • Every widget, Live Activity, Control Center and Siri action is a LiveActivityIntent. iOS runs it in the app process without bringing the app to the foreground. The intent:
    1. gets one location fix with CLLocationUpdate (when the policy needs it)
    2. calls rpc/omni_clock
    3. starts or ends the Live Activity
    4. reloads widget timelines
  • The widget extension never holds auth tokens. Its timelines render the App Group snapshot with Text(timerStart, style: .timer), so the elapsed time ticks without reloads.

Android

  • The react-native-android-widget task handler runs headless JS when the button is tapped. It:
    1. reads the session from expo-secure-store
    2. gets a location with expo-location (getCurrentPositionAsync, balanced accuracy)
    3. calls clock through @omni/api
    4. re-renders the widget
  • While the user is checked in, a Notifee foreground-service notification shows a native Chronometer.
  • The Quick Settings tile is a small Kotlin Expo module that sends a broadcast to the same headless task.

Notifications

  • push_tokens(user_id, platform, token) are registered by the apps.
  • Events go into notifications (the in-app inbox). A DB webhook triggers the push-dispatch Edge Function, which sends to APNs (token-based auth) and FCM HTTP v1, honoring each user's preferences and quiet hours.
  • Scheduled reminders ("shift starts in 10 min", "missed check-out") run in pg_cron and insert notifications.

SSO

  • Supabase Auth SAML 2.0 providers are registered per org domain (supabase sso add). The org admin UI stores the metadata URL and domains in org_sso_domains.
  • The sign-in screen looks up the email domain and calls signInWithSSO({ domain }).
  • On first SSO login, the user is auto-joined to the org that owns the domain, with the default role employee.

Repos & tooling

  • pnpm workspaces + Turborepo, Biome (lint and format), Vitest (unit), and Playwright (web e2e).
  • The Supabase CLI handles local DB, migrations and type generation.
  • iOS is a SwiftPM package built with xtool (apps/ios/Package.swift + xtool.yml). Swift tokens are generated from @omni/design-tokens.
  • Hosting: the web app on Vercel, the backend on Supabase. EAS builds Android; Xcode Cloud or fastlane builds iOS.

Data residency

Supabase-hosted regions closest to KSA are me-central-1 (UAE) and eu-central-1. If a customer needs PDPL in-Kingdom residency, the same stack can be self-hosted in a Saudi cloud region; nothing in the code depends on Supabase-hosted-only features, apart from SAML, which self-hosted GoTrue also supports.

Database

Migration Contents
20261027000001_core_schema.sql Enums, orgs, profiles, members, departments, sites (PostGIS plus generated lat/lng), employees, shift templates, attendance policies, assignments, attendance events, invites, SSO domains
20261027000002_rls.sql private.* membership helpers, RLS on every table, column-level update grants on attendance, and the people_directory view (no salary or IDs)
20261027000003_rpc.sql New-user trigger (profile plus SSO domain auto-join), my_context, set_active_org, create_organization, create_invite, redeem_invite, clock_status, clock, and the policy/shift resolver private.effective_assignment
20261027000004_notifications.sql push_tokens, notifications, and a trigger that notifies the manager about a flagged check-in

Policy resolution: the most specific active assignment wins, in this order: employee › department › site › company. Within a scope, an assignment with a start date beats an open-ended one, so date-bounded presets like Ramadan apply automatically.

Tests: supabase/tests/run-local.sh sets up a throwaway Postgres + PostGIS with a minimal auth shim, then applies the migrations and seed and runs supabase/tests/*_test.sql. The tests cover:

  • org isolation
  • salary privacy
  • invite redemption
  • the clock state machine and idempotency
  • strict, flexible and off geofencing
  • late-sync flagging
  • tamper protection
  • policy precedence
On this page
  • Packages
  • Multi-tenancy & security
  • Check-in from widgets
  • Notifications
  • SSO
  • Repos & tooling
  • Data residency
  • Database