.. _xcode_cloud_app_spec: ============================== ``app_spec.json`` Reference ============================== ``app_spec.json`` sits at the root of a client app repository and drives the ``patch_and_tag_for_xcode_build.py`` script (``peek_field_app/xcode-cloud-template/``). Running the script before tagging a build stamps all client-specific values into the Angular project files, the Xcode project, and the server-info defaults asset — so the built ``App.ipa`` is pre-configured for that deployment with no manual edits required. Shape ===== .. code-block:: json { "appName": "peek", "appId": "com.synerty.peek", "appIcon": "resources/icon.png", "appDotVersion": "0.0.0", "teamId": null, "appearance": { "backgroundColor": "#26abe2", "environmentName": null }, "serverConnection": { "host": null, "useSsl": false, "httpPort": 8000, "websocketPort": 8000, "hasConnected": false }, "oauth2": { "appId": null, "tenantId": null } } Field Reference =============== +------------------------------+-----------------------------------------------+ | Field | Description | +==============================+===============================================+ | ``appId`` | iOS bundle identifier, e.g. | | | ``com.synerty.peek`` or | | | ``com.synerty.peek.acme`` | +------------------------------+-----------------------------------------------+ | ``teamId`` | Apple Developer team ID for code signing | +------------------------------+-----------------------------------------------+ | ``appearance.`` | Hex colour used as the app's primary / | | ``backgroundColor`` | splash colour | +------------------------------+-----------------------------------------------+ | ``appearance.`` | Short label shown on the config screen; | | ``environmentName`` | **must match the git branch name** | +------------------------------+-----------------------------------------------+ | ``serverConnection.host`` | Peek backend hostname (no scheme or port) | +------------------------------+-----------------------------------------------+ | ``serverConnection.useSsl`` | ``true`` for HTTPS / WSS connections | +------------------------------+-----------------------------------------------+ | ``serverConnection.`` | HTTP port — ``443`` with SSL, ``8000`` | | ``httpPort`` | for direct | +------------------------------+-----------------------------------------------+ | ``serverConnection.`` | WebSocket port — typically same as | | ``websocketPort`` | ``httpPort`` | +------------------------------+-----------------------------------------------+ | ``serverConnection.`` | ``true`` skips the Connect screen on first | | ``hasConnected`` | launch | +------------------------------+-----------------------------------------------+ | ``oauth2.appId`` | Azure Entra **Application (client) ID** | +------------------------------+-----------------------------------------------+ | ``oauth2.tenantId`` | Azure Entra **Directory (tenant) ID** | +------------------------------+-----------------------------------------------+ What the Script Writes ====================== The script writes the following ``server-info-tuple-defaults.json`` asset (``peek_field_app/src/assets/peek_core_device/``): +------------------------------+-------------------------------------------+ | ``app_spec.json`` | ``server-info-tuple-defaults.json`` | +==============================+===========================================+ | ``serverConnection.host`` | ``host`` | +------------------------------+-------------------------------------------+ | ``serverConnection.useSsl`` | ``useSsl`` | +------------------------------+-------------------------------------------+ | ``serverConnection.httpPort``| ``httpPort`` | +------------------------------+-------------------------------------------+ | ``serverConnection.`` | ``websocketPort`` | | ``websocketPort`` | | +------------------------------+-------------------------------------------+ | ``serverConnection.`` | ``hasConnected`` | | ``hasConnected`` | | +------------------------------+-------------------------------------------+ | ``oauth2.appId`` | ``oauth2AppId`` | +------------------------------+-------------------------------------------+ | ``oauth2.tenantId`` | ``oauth2TenantId`` | +------------------------------+-------------------------------------------+ | ``appId`` *(top-level)* | ``oauth2BundleId`` | +------------------------------+-------------------------------------------+ It also updates: - ``ios/App/App.xcodeproj/project.pbxproj`` — ``PRODUCT_BUNDLE_IDENTIFIER`` and ``DEVELOPMENT_TEAM`` - ``src/environments/peek-app-environment.ts`` — version string - ``app_spec.json`` — ``appDotVersion`` bumped to the new git tag Azure Application Proxy — ``host`` and ``oauth2.appId`` ========================================================= When the Peek backend is published through Azure Application Proxy, ``serverConnection.host`` and ``oauth2.appId`` must belong to the same Entra app registration. ``host`` The **Application Proxy external hostname** (without scheme), e.g. ``peekfieldapp-synerty.msappproxy.net``. The field app connects to this host for both HTTP and WebSocket traffic. ``oauth2.appId`` The **Application (client) ID** of the Entra app registration that owns the Application Proxy endpoint at ``host``. The iOS app uses this ID to drive the MSAL authentication flow against Entra before the proxy grants access. ``oauth2BundleId`` (derived from ``appId``) The iOS bundle identifier is used by MSAL to construct the redirect URI ``msauth.://auth``. That exact URI must be registered on the Entra app registration under the **iOS / macOS** platform — see :ref:`azure_proxy_setup`. Because ``oauth2BundleId`` is derived from the top-level ``appId``, changing the bundle ID requires updating both the Xcode project and the Entra registration. The OAuth2 scope the app requests depends on the identifier URI configured on the Entra app registration: - ``https://`` (recommended for Application Proxy) → scope ``https:///user_impersonation`` - ``api://`` → scope ``api:///user_impersonation`` The ``https://`` format is recommended because it keeps the scope human-readable and ties it directly to the proxy URL. Example ======= ``app_spec.json`` for the PeekFieldApp Azure proxy deployment: .. code-block:: json { "appName": "peek", "appId": "com.synerty.peek", "teamId": "Q55F59LQD3", "appearance": { "backgroundColor": "#26abe2", "environmentName": "PROD" }, "serverConnection": { "host": "peekfieldapp-synerty.msappproxy.net", "useSsl": true, "httpPort": 443, "websocketPort": 443, "hasConnected": true }, "oauth2": { "appId": "908c7b2f-e757-46be-9c6c-03da026a4159", "tenantId": "253aed61-d766-447d-8c13-44d7e0daeeb9" } } Running the script produces the following ``server-info-tuple-defaults.json``: .. code-block:: json { "host": "peekfieldapp-synerty.msappproxy.net", "useSsl": true, "httpPort": 443, "websocketPort": 443, "hasConnected": true, "oauth2AppId": "908c7b2f-e757-46be-9c6c-03da026a4159", "oauth2TenantId": "253aed61-d766-447d-8c13-44d7e0daeeb9", "oauth2BundleId": "com.synerty.peek" } On first launch the iOS app reads this asset, skips the Connect screen (``hasConnected: true``), and immediately starts the MSAL flow against the Entra tenant. The redirect URI ``msauth.com.synerty.peek://auth`` must be registered under **iOS / macOS** on the app registration — see :ref:`azure_proxy_setup`. Running the Script ================== From the **repo root** (the directory that contains ``app_spec.json`` and ``peek_field_app/``):: pip install gitpython python peek_field_app/xcode-cloud-template/patch_and_tag_for_xcode_build.py The script validates that the active git branch name matches ``appearance.environmentName``, then stamps all project files, commits, and creates a version tag. Xcode Cloud detects the new tag and starts a build automatically. Troubleshooting =============== **"Safari cannot open the page because the address is invalid" after Microsoft login.** The ``CFBundleURLSchemes`` entry in ``Info.plist`` does not match the iOS bundle identifier. After a successful Entra login, MSAL constructs the redirect URI as ``msauth.://auth`` using the runtime bundle ID. If ``Info.plist`` still registers the base bundle ID (e.g. ``msauth.com.synerty.peek``) instead of the client-specific one (e.g. ``msauth.com.synerty.peek.nzor1pek6``), iOS has no handler for the redirect URI and Safari shows this error. Fix — update the MSAL entry in ``peek_field_app/ios/App/App/Info.plist`` to match the bundle ID: .. code-block:: xml CFBundleURLName com.synerty.peek CFBundleURLSchemes msauth.com.synerty.peek CFBundleURLName com.synerty.peek.nzor1pek6 CFBundleURLSchemes msauth.com.synerty.peek.nzor1pek6 ``patch_and_tag_for_xcode_build.py`` performs this update automatically using ``plistlib`` — it finds the ``CFBundleURLTypes`` entry whose scheme starts with ``msauth.`` and replaces both the scheme and the ``CFBundleURLName`` with ``msauth.`` and ```` respectively. This only requires the ``appId`` field in ``app_spec.json`` to be correct.