Skip to content

dhwani-ris/frappe-mobile-control

Mobile Control

Mobile Control - Custom Frappe Application

Installation

You can install this app using the bench CLI:

cd $PATH_TO_YOUR_BENCH
bench get-app $URL_OF_THIS_REPO --branch develop
bench install-app mobile_control

Mobile Auth API

These endpoints are exposed as whitelisted methods and are intended for a mobile client. All endpoints expect POST requests and use the api/v2/method/ route.

Endpoints:

  • mobile_auth.login - Login with username/password.
  • mobile_auth.logout - Logout and revoke refresh tokens.
  • mobile_auth.send_login_otp - Send OTP to mobile number for login.
  • mobile_auth.verify_login_otp - Verify OTP and complete login.
  • mobile_auth.refresh_token - Refresh access token using refresh token.
  • mobile_auth.permissions - Get current user permissions (requires authentication).
  • mobile_auth.get_translations - Get translation dictionary for one or more languages (requires authentication). By default returns DB translations only; use all=1 for full (apps + DB). Use lang=hi,en for multiple languages.
  • mobile_auth.get_social_login_providers - Discover enabled social providers from Social Login Key (guest).
  • mobile_auth.get_social_authorize_url - Build provider-direct OAuth authorize URL for one-tap social login (guest).

Response tokens:

  • access_token expires in 24 hours.
  • refresh_token expires in 30 days and is rotated on every refresh.

Social login APIs (one-tap)

These APIs are designed for mobile SDK OAuth (PKCE) login and avoid the extra provider selection click on Frappe login page.

  • Providers are auto-discovered from Frappe Social Login Key.
  • No extra provider config is required in mobile_control.
  • Sensitive data such as client_secret is never returned.

Required mobile callback URI:

  • frappemobilesdk://oauth/callback

Provider discovery:

GET /api/method/mobile_auth.get_social_login_providers

Example response:

{
  "providers": [
    { "id": "google", "label": "Google", "icon_url": "https://..." },
    { "id": "microsoft", "label": "Microsoft", "icon_url": null }
  ]
}

Provider-direct authorize URL:

POST /api/v2/method/mobile_auth.get_social_authorize_url

Body:

{
  "provider": "google",
  "client_id": "mobile-client-id",
  "redirect_uri": "frappemobilesdk://oauth/callback",
  "scope": "openid all",
  "state": "random-state",
  "code_challenge": "pkce-code-challenge",
  "code_challenge_method": "S256"
}

Example response:

{
  "authorize_url": "https://accounts.google.com/o/oauth2/v2/auth?...state=<encoded-with-redirect_to>"
}

Notes:

  • Returned URL is generated by Frappe Social Login flow for the selected provider.
  • client_id in request body is your Frappe OAuth Client ID (not Google client id).
  • After social login succeeds, Frappe redirects to its OAuth authorize endpoint, which then redirects to redirect_uri (frappemobilesdk://oauth/callback) with authorization code.

Troubleshooting:

  • Provider not showing: verify Social Login Key exists and social login is enabled for that key.
  • Invalid provider error: ensure provider matches discovered provider id (google, microsoft, etc).
  • Redirect URI mismatch: ensure app sends frappemobilesdk://oauth/callback (or add allowlisted URIs in site config).
  • Social opens wrong provider or fails immediately: verify Social Login Key for that provider is enabled and configured with valid client credentials.

Auth response shape (login, verify OTP, refresh token)

Login, mobile_auth.verify_login_otp, and mobile_auth.refresh_token return a response like:

{
  "message": "Logged In",
  "user": "user@example.com",
  "full_name": "User Name",
  "language": "en",
  "access_token": "...",
  "refresh_token": "...",
  "offline_enabled": false,
  "mobile_form_names": [
    {
      "mobile_workspace_item": "Mobile Refresh Token",
      "group_name": "",
      "doctype_meta_modified_at": "2026-02-14 14:40:49.962439",
      "doctype_icon": ""
    }
  ],
  "roles": ["Mobile User", "All", "Desk User"],
  "permissions": [
    {
      "doctype": "Mobile Refresh Token",
      "read": true,
      "write": false,
      "create": true,
      "delete": false,
      "submit": false,
      "cancel": false,
      "amend": false
    }
  ]
}
  • language is the user's language (default "en" if blank).
  • offline_enabled is the value of the Mobile Configuration.offline_enabled Check field. The mobile SDK uses this to decide whether to run as an offline-first client or a thin online client. Default false. Only emitted when the parent enabled flag is on. See the SDK's doc/OFFLINE_MODE_TOGGLE.md for the full client-side contract.
  • roles is an array of role names.
  • permissions is an array of objects; each has doctype and the flags read, write, create, delete, submit, cancel, amend.

Client flow:

  1. Login or OTP verify to receive access_token + refresh_token.
  2. Use access_token as Authorization: Bearer <access_token> for API calls.
  3. When access token expires, call mobile_auth.refresh_token with the refresh_token to get a new pair.

User permissions:

  • Permissions are automatically included in login, OTP verify, and refresh token responses.
  • Permissions include user roles and doctype-level permissions (read, write, create, delete, submit, cancel, amend) for mobile-configured doctypes.
  • To refresh permissions without re-authenticating, call mobile_auth.permissions endpoint.

Translations:

  • Call GET /api/method/mobile_auth.get_translations?lang=hi (Bearer token required). Omit lang for English (en). By default only DB translations (Translation doctype) are returned. Add &all=1 or &all=true to get full translations (apps CSV/MO + DB). For multiple languages, use comma-separated codes: ?lang=hi,en. Response is always the same shape: { "langs": ["hi"], "translations_by_lang": { "hi": { "source text": "translated text", ... } } } (or multiple keys in langs and translations_by_lang). Use translations_by_lang[lang][source] ?? source for lookup.

Request Examples

All requests use:

POST {{base_url}}/api/v2/method/<endpoint>

Login:

{
  "username": "your.username",
  "password": "your.password"
}

Send OTP:

{
  "mobile_no": "+15551234567"
}

Verify OTP:

{
  "tmp_id": "TMP_ID_FROM_SEND_OTP",
  "otp": "123456"
}

Refresh token:

{
  "refresh_token": "REFRESH_TOKEN"
}

Logout:

Authorization: Bearer <access_token>

Get permissions:

GET {{base_url}}/api/method/mobile_auth.permissions
Authorization: Bearer <access_token>

Response:

{
  "roles": ["Mobile User", "System Manager"],
  "permissions": [
    {
      "doctype": "Customer",
      "read": true,
      "write": true,
      "create": true,
      "delete": false,
      "submit": true,
      "cancel": false,
      "amend": false
    }
  ]
}

Bruno Collection

The API/ directory contains a Bruno collection to try the mobile auth endpoints locally.

Collection: API/bruno.json

Requests:

File Description
Login with username and password.bru POST login with username/password; returns access_token, refresh_token, roles, permissions, language.
Login with mobile.bru POST send OTP to mobile number (mobile_auth.send_login_otp).
Login with mobile verify.bru POST verify OTP and login (mobile_auth.verify_login_otp).
Get Access Token.bru POST refresh token to get new access_token and refresh_token.
Logout.bru POST logout (Bearer token required); revokes refresh tokens.
permissions.bru GET current user roles and permissions (Bearer token required).
get_translations.bru GET translation dictionary; optional ?lang=hi (Bearer token required).
App Status.bru GET app status (enabled, package_name, app_title, version). Guest.
App Configuration.bru GET mobile configuration list. Guest.

Setup: Set base_url in collection/environment variables. For auth requests, set username, password, and after login use the returned access_token as Bearer token in subsequent requests (or use Bruno’s response scripts to save the token).

Mobile Error Log

When a mobile push permanently fails on the server (a terminal 4XX, terminal 401, or 5XX), the SDK records the exact failing request and posts an aggregated report here. The goal is to let a developer reconstruct a replayable request — same payload, same user, same permissions — without asking the field user for steps to reproduce. Capture is automatic on the device; the host app does not wire anything up.

Two doctypes back the feature:

DocType Purpose
Mobile Error Log One row per unique failure signature. Holds the failing DocType, operation, HTTP status, exception type, request method/URL, the reporting user (raw value + User link), their roles, trace id, app version, device, an occurrence_count, and last_seen. Read-only; System Manager read/delete only.
Mobile Error Log Example (child) A rolling window of the 5 most recent occurrences for a signature. Each holds the mobile_uuid, occurred_at, the JSON request_payload, the server response_body, and a rendered curl_command.

mobile_sync.report_error

POST (whitelisted). Resolves to mobile_control.api.error_log.report_error. Takes a single payload argument — the JSON object the SDK's ErrorLogCollector aggregates per drain:

{
  "signature": "<client-computed stable hash of the failure>",
  "doctype_name": "Household Survey",
  "operation": "update",
  "http_status": 417,
  "exc_type": "...",
  "request_method": "PUT",
  "request_url": "https://.../api/resource/Household%20Survey/...",
  "error_user": "Administrator",
  "error_user_roles": ["System Manager", "..."],
  "trace_id": "<X-Frappe-Request-Id, if server monitoring is on>",
  "app_version": "...",
  "device": "...",
  "occurrence_count": 1,
  "examples": [
    {
      "mobile_uuid": "...",
      "occurred_at": "2026-06-12 12:00:00",
      "request_payload": "<verbatim request body JSON>",
      "response_body": "<verbatim server response>"
    }
  ]
}

Behaviour:

  • UPSERT by signature — the signature is taken verbatim and never recomputed server-side. A repeat failure increments occurrence_count and refreshes the volatile fields (roles, trace id, last_seen) rather than creating a new row.
  • Last-5 examples — incoming examples are appended and evicted to the most recent 5 per signature.
  • signature is required; a missing/blank one is a ValidationError.
  • Saved with ignore_links (a deactivated or renamed error_user must never block the insert) and ignore_permissions (the doctype has no create permission by design).

Returns {"status": "ok", "name": "<row name>"}.

Replaying a failure

Each example stores a curl_command rendered from its method, URL, and payload. The host and auth token are never stored — they are emitted as {{HOST}} and {{TOKEN}} placeholders. To replay: substitute your own bench host and a token for the same user (error_user), then run the CURL to reproduce the original server response.

Retention

Rows are purged daily by mobile_control.tasks.purge_mobile_error_logs. Retention is configurable on Mobile Configuration via mobile_error_log_retention_days (default 30); rows older than that are deleted.

Contributing

This app uses pre-commit for code formatting and linting. Please install pre-commit and enable it for this repository:

cd apps/mobile_control
pre-commit install

Pre-commit is configured to use the following tools for checking and formatting your code:

  • ruff
  • eslint
  • prettier
  • pyupgrade

Maintainers logo

About

Mobile Control - Custom Frappe Application

Resources

License

Code of conduct

Contributing

Security policy

Stars

20 stars

Watchers

3 watching

Forks

Packages

 
 
 

Contributors