SuperAPI AI Directory

ping-orchestration-ios-sdk

General & other

View repository

Updated 10-16 08:05

About this skill

Skill Parameters

This skill accepts an optional argument string when invoked as /ping-orchestration-ios-sdk <args>.

No-arg Invocation — Wizard Mode

When invoked with no arguments (/ping-orchestration-ios-sdk), run the wizard:

Step W1 — Determine intent using AskUserQuestion:

"What would you like to do?"
Options:
  A) Build a new sample app      — scaffold a complete Xcode project with full Ping branding
  B) Integrate into existing app — generate only the SDK files to drop into your project
  C) Browse the reference guide  — show the full SDK reference
  D) Something else              — let me describe what I need
  • A / B → Step W1b, then Step W2
    • A: follow with Automated Project Creation (read assets/project-scaffolding.md)
    • B: generate SDK integration files only (no Xcode project scaffolding)
  • C → display the reference guide from "Ping SDK for iOS — Integration Guide" onward. Stop.
  • D → follow-up free-text question, then route accordingly.

Step W1b — Journey export offer (options A and B only).

Ask:

"Do you have a Journey export JSON file? If so, paste it or attach it and I'll analyse it to generate a sample that handles exactly the callbacks your Journey uses."

  • If provided: run Section 13 (Journey Export Analysis) before Step W2. Pre-populate journeyName, callbackTier, and serverUrl from the analysis results.
  • If not: continue to Step W2 as normal.

Step W2 — Collect configuration (required for A and B).

First determine flowType if not already known, then ask only the parameters relevant to that flow in a single AskUserQuestion call. Show defaults where they exist. Do not proceed until every required field has a value.

Common parameters (all flows):

Parameter Required Default Description
appName Yes — App name used for Swift types, PingHeaderView title, and bundle ID. E.g. PingDemo
outputPath Yes (option A only) — Directory to write the Xcode project (absolute path)
flowType Yes — oidc-web · journey · davinci
clientId Yes — OAuth 2.0 Client ID
redirectUri Yes — OAuth 2.0 redirect URI (custom scheme, e.g. myapp://callback)
discoveryEndpoint Yes — Full OIDC discovery URL ending in /.well-known/openid-configuration
scopes No openid profile email OAuth 2.0 scopes, space-separated

Journey-only additional parameters:

Parameter Required Default Description
serverUrl Yes — PingAM/AIC server base URL (no trailing /)
realm No alpha Authentication realm
cookieName No iPlanetDirectoryPro Session cookie name
journeyName No Login Journey tree name
callbackTier Yes — basic, standard, or full (see Section 12)

DaVinci-only additional parameters:

Parameter Required Default Description
callbackTier Yes — basic, standard, or full (see Section 12)
acrValues No (none) Optional ACR values string — omit entirely if not provided

OIDC Web-only additional parameters:

Parameter Required Default Description
acrValues No (none) Optional ACR values string — omit entirely if not provided

Present callbackTier options (Journey and DaVinci only):

  • Basic — NameCallback, PasswordCallback, TextOutputCallback, ChoiceCallback (Journey) / TextCollector, PasswordCollector, SubmitCollector, LabelCollector, FlowCollector (DaVinci). Minimal dependencies.
  • Standard — Everything in Basic plus: attribute callbacks, FIDO, Protect, Device Binding/Profile, SelectIdp, T&C, KBA, PollingWait, ConsentMapping. No third-party social SDKs.
  • Full fat — Everything in Standard plus IdpCallback/IdpCollector/SocialCollectorView (social login) and ReCaptchaEnterpriseCallback. Requires Facebook, Google, and ReCaptcha Enterprise SPM dependencies.

Validation rules:

  • redirectUri must use a custom scheme (not http:// or https://).
  • discoveryEndpoint must start with https:// and end with /.well-known/openid-configuration. If the user provides a base AM URL, construct <serverUrl>/oauth2/<realm>/.well-known/openid-configuration and confirm.
  • scopes must include openid. If missing, prepend it and warn.
  • serverUrl (Journey only) must start with https:// and have no trailing /.

Step W3 — Confirm and generate. Summarise collected values in a short table, ask "Ready to generate — does this look right?", then proceed on confirmation.


Parameters (with-args invocation)

Parameter Syntax Purpose
create-sample create-sample "<description>" Generate a complete runnable sample app
app-name app-name "<name>" Set the app name. Defaults to "MyApp" if omitted.
output-path output-path "<path>" Write files to disk. If omitted, print inline.

create-sample "<description>"

  1. Analyse the description — identify flow type(s), modules, and implied screens.

  2. Journey export offer — if the flow type is journey (or could be), ask:

    "Do you have a Journey export JSON? I can analyse it and tailor the sample to exactly the callbacks your Journey uses." If provided, run Section 13 before proceeding. Use its output to determine callbackTier and screens.

  3. Ask one clarifying question only if the flow type is genuinely ambiguous. Collect required parameters for the detected flow type (see W2 table) in a single AskUserQuestion with an explicit "I can use placeholders" option.

  4. Resolve the app name from app-name or use "MyApp".

  5. Generate using the template files in assets/. Substitute all PLACEHOLDER_* tokens (see table below). Use @Observable @MainActor ViewModels — never ObservableObject/@Published. Apply Ping Identity branding (Section 11).

    OIDC Web flow — read and substitute: App.swift.oidc.template, AppOidc.swift.template, OidcLoginViewModel.swift.template, ContentView.swift.oidc.template, LoginView.swift.oidc.template, AuthenticatedView.swift.oidc.template, Theme.swift.template

    Journey flow — read and substitute: App.swift.journey.template, AppJourney.swift.template, JourneyLoginViewModel.swift.template, ContentView.swift.journey.template, LoginView.swift.journey.template, AuthenticatedView.swift.journey.template, ContinueNodeView.swift.journey.template, Theme.swift.template

    Then, for each callback in callbackTier, read and substitute the matching file from assets/callbacks/:

    • Basic: NameCallbackView, PasswordCallbackView, ChoiceCallbackView, TextOutputCallbackView
    • Standard: everything in Basic plus TextInputCallbackView, ValidatedUsernameCallbackView, ValidatedPasswordCallbackView, StringAttributeInputCallbackView, NumberAttributeInputCallbackView, BooleanAttributeInputCallbackView, ConfirmationCallbackView, TermsAndConditionsCallbackView, KbaCreateCallbackView, ConsentMappingCallbackView, PollingWaitCallbackView, SelectIdpCallbackView, FidoRegistrationCallbackView, FidoAuthenticationCallbackView, DeviceProfileCallbackView, DeviceBindingCallbackView, DeviceSigningVerifierCallbackView, PingOneProtectInitializeCallbackView, PingOneProtectEvaluationCallbackView
    • Full fat: everything in Standard plus IdpCallbackView, ReCaptchaEnterpriseCallbackView

    In ContinueNodeView.swift.journey.template, replace // PLACEHOLDER_CALLBACK_CASES with the appropriate switch cases for the tier, and replace // PLACEHOLDER_TIER_IMPORTS with the matching import statements. Replace // PLACEHOLDER_SELF_ADVANCING_TYPES with the self-advancing types for the tier.

    DaVinci flow — read and substitute: App.swift.davinci.template, AppDaVinci.swift.template, DaVinciLoginViewModel.swift.template, ContentView.swift.davinci.template, LoginView.swift.davinci.template, AuthenticatedView.swift.davinci.template, CollectorNodeView.swift.davinci.template, Theme.swift.template

    Then, for each collector in callbackTier, read and substitute the matching file from assets/collectors/:

    • Basic: TextCollectorView, PasswordCollectorView, SubmitCollectorView, LabelCollectorView, FlowCollectorView
    • Standard: everything in Basic plus MultiSelectCheckBoxView, MultiSelectComboBoxView, SingleSelectDropdownView, SingleSelectRadioView, PhoneNumberCollectorView, ProtectCollectorView, FidoRegistrationCollectorView, FidoAuthenticationCollectorView, DeviceRegistrationCollectorView, DeviceAuthenticationCollectorView
    • Full fat: everything in Standard plus SocialCollectorView

    In CollectorNodeView.swift.davinci.template, replace // PLACEHOLDER_COLLECTOR_CASES with the appropriate switch cases for the tier, and replace // PLACEHOLDER_TIER_IMPORTS and // PLACEHOLDER_SELF_ADVANCING_TYPES accordingly.

  6. Deliver:

    • With output-path: read assets/project-scaffolding.md and follow Steps 0–G exactly. For SPM products per flow type, see Step C in that document.
    • Without output-path: print every file inline with a // --- <Filename>.swift --- header.

Template Placeholders

Placeholder Replaces with
PLACEHOLDER_APP_NAME Resolved app name (e.g. PingDemo)
PLACEHOLDER_APP_NAME_UPPER Upper-cased app name (e.g. PINGDEMO) — used in Keychain account key construction
PLACEHOLDER_CLIENT_ID clientId value
PLACEHOLDER_REDIRECT_URI redirectUri value
PLACEHOLDER_DISCOVERY_ENDPOINT discoveryEndpoint value
PLACEHOLDER_SCOPES Scopes as comma-separated quoted strings for Set([...]) (e.g. "openid", "profile", "email")
PLACEHOLDER_SERVER_URL serverUrl (Journey/DaVinci only)
PLACEHOLDER_REALM realm (Journey/DaVinci only)
PLACEHOLDER_COOKIE_NAME cookieName (Journey/DaVinci only)
PLACEHOLDER_JOURNEY_NAME journeyName (Journey only, default "Login")
PLACEHOLDER_ACR_VALUES_LINE DaVinci/OIDC: entire oidcConfig.acrValues = "..." line — omit the line entirely if not provided
PLACEHOLDER_KEYCHAIN_ACCOUNT Keychain account key — use "<APP_NAME_UPPER>_ACCESS_TOKEN" pattern (e.g. "PINGDEMO_ACCESS_TOKEN")

When the user chose placeholder mode: use "yourClientId", "yourapp://callback", "https://your-tenant.forgeblocks.com/am/oauth2/alpha/.well-known/openid-configuration".

Automated Project Creation (when output-path is provided)

Read assets/project-scaffolding.md and follow Steps 0–G exactly. Key reminders:

  • Use deployment target 18.0.
  • Layout is flat — .xcodeproj, source dir, and Config/ are siblings directly under <output-path>/.
  • PBXFileSystemSynchronizedRootGroup means Swift files dropped in the source dir are included automatically.
  • Use project.objects.each (not project.root_object.objects) to iterate all project objects in Ruby scripts.

Examples:

/ping-orchestration-ios-sdk create-sample "a simple username/password login using Journey"
/ping-orchestration-ios-sdk create-sample "OIDC centralized login with token display and sign-out" app-name "PingDemo" output-path "/Users/me/Dev"
/ping-orchestration-ios-sdk create-sample "Journey login with FIDO passkey registration and biometric auth" app-name "FidoSample"

Ping SDK for iOS — Integration Guide

Naming Conventions for Code Examples

Always use full, descriptive variable and parameter names. Never abbreviate: vm → viewModel, cb → nameCallback, cfg → journeyConfig. This applies to local bindings in switch statements and closure arguments (never use $0).

Flow Types — Choose Your Integration Path

Journey (native/embedded) — Authentication UI rendered entirely inside your app using SwiftUI views. No browser launched. onOpenURL / OpenURLMonitor are not needed.

OIDC Web (OidcWebClient) — Uses SFSafariViewController or ASWebAuthenticationSession. Must wire onOpenURL → OpenURLMonitor.shared.handleOpenURL(url) and handle foreground resumption.

DaVinci — Like Journey (native), but uses collectors instead of callbacks. Social IdP collectors (IdpCollector) do launch a browser, so wire onOpenURL if your DaVinci flow uses them.

Module Map

Pick only what you need — the SDK is modular:

Module Import When to add
PingOrchestrate Core engine — Node, ContinueNode, SuccessNode, FailureNode, ErrorNode Always (import explicitly in any file using node types)
PingJourneyPlugin Callback protocol Any file that declares any Callback parameters
PingJourney import PingJourney PingOne AIC / AM Journey flows, all concrete callback types
PingDavinci import PingDavinci PingOne DaVinci flows
PingDavinciPlugin import PingDavinciPlugin Collector protocol — any file using any Collector or switching on collector types
PingOidc import PingOidc OIDC / OAuth token handling, User / userinfo, OidcWebClient
PingBrowser import PingBrowser Centralized login — BrowserType, BrowserMode, BrowserLauncher, OpenURLMonitor
PingLogger import PingLogger Unified logging
PingStorage import PingStorage Keychain token/session storage
PingExternalIdP import PingExternalIdP Social login (Apple/Google/Facebook)
PingFido import PingFido FIDO2 / WebAuthn
PingProtect import PingProtect PingOne Protect risk signals
PingBinding import PingBinding Device binding & signing
PingOath import PingOath TOTP / HOTP MFA
PingPush import PingPush Push notification MFA

1 — SDK Package Reference

.package(url: "https://github.com/ForgeRock/ping-ios-sdk", from: "2.0.0")
// Add only the products your target needs:
.product(name: "PingJourney", package: "ping-ios-sdk"),
.product(name: "PingOidc", package: "ping-ios-sdk"),
.product(name: "PingBrowser", package: "ping-ios-sdk"),   // OidcWebClient flows

CocoaPods:

pod 'PingJourney'
pod 'PingStorage'
pod 'PingLogger'

2 — Node Type Reference

// PingOrchestrate — import in any file pattern-matching on nodes

struct FailureNode: Node {
    let cause: Error          // NON-OPTIONAL — use cause.localizedDescription
}
struct ErrorNode: Node {
    let message: String       // NON-OPTIONAL
    let status: Int?
}
struct SuccessNode: Node {
    let session: Session      // NON-OPTIONAL
}
// ContinueNode — more input needed (callbacks for Journey, collectors for DaVinci)

Node is NOT Equatable — track state changes via separate Bool flags:

var isRegistered = false   // set in next() based on returned node type

3 — Callback Property Types

Journey callback properties are non-optional Strings. Do not use ?? fallback. Basic-tier properties: NameCallback.name/prompt, PasswordCallback.password/prompt, TextInputCallback.text/prompt, ChoiceCallback.selectedIndex/choices, TextOutputCallback.message/messageType.

Standard-tier gotchas (compile errors if guessed wrong): ValidatedUsernameCallback → .username (NOT .value); ValidatedPasswordCallback → .password; ConfirmationCallback → .options/.selectedIndex: Int?; TermsAndConditionsCallback → .accepted: Bool; KbaCreateCallback → .selectedQuestion/.selectedAnswer; SelectIdpCallback → .value = provider.provider (no .setProvider()).

Read references/journey-callbacks.md for the complete Critical API Facts section before writing any Standard-tier callback view.

4 — AppJourney Singleton

import PingJourney
import PingOrchestrate
import PingLogger

@MainActor
class AppJourney {
    static let shared = AppJourney()

    let journey: Journey

    private init() {
        journey = Journey.createJourney { journeyConfig in
            journeyConfig.serverUrl = "https://your-server.example.com/am"
            journeyConfig.realm     = "alpha"
            journeyConfig.cookie    = "your-cookie-name"
            journeyConfig.logger    = LogManager.standard
            journeyConfig.module(PingJourney.OidcModule.config) { oidcConfig in
                oidcConfig.clientId          = "yourClientId"
                oidcConfig.scopes            = ["openid", "profile", "email"]
                oidcConfig.redirectUri       = "yourapp://callback"
                oidcConfig.discoveryEndpoint = "https://your-server.example.com/am/oauth2/alpha/.well-known/openid-configuration"
                oidcConfig.logger            = LogManager.standard
            }
        }
    }
}

The journeyName collected in the wizard is passed to journey.start() at call time:

node = await AppJourney.shared.journey.start(journeyName)

For multi-flow apps (Journey + DaVinci + OIDC Web) and token storage customisation, see references/advanced-patterns.md.

5 — Journey Authentication Flow

ViewModel pattern

import Foundation
import Observation
import PingJourney
import PingOrchestrate

@Observable
@MainActor
final class LoginViewModel {
    var node: Node?
    var isLoading = false

    let journeyName: String

    init(journeyName: String = "Login") {
        self.journeyName = journeyName
    }

    func start() async {
        isLoading = true
        defer { isLoading = false }
        node = await AppJourney.shared.journey.start(journeyName)
    }

    func next(continueNode: ContinueNode) async {
        isLoading = true
        defer { isLoading = false }
        node = await continueNode.next()
    }

    func reset() { node = nil }
}

Own the model with @State, not @StateObject:

@State private var loginViewModel = LoginViewModel()

Top-Level Node Switch

import PingOrchestrate
import PingJourney

switch loginViewModel.node {
case let successNode as SuccessNode:
    AuthenticatedView(successNode: successNode, onSignOut: loginViewModel.reset)
case let failureNode as FailureNode:
    ErrorView(message: failureNode.cause.localizedDescription, onRetry: retry)
case let errorNode as ErrorNode:
    ErrorView(
        message: errorNode.message.isEmpty ? "An error occurred." : errorNode.message,
        onRetry: retry
    )
case let continueNode as ContinueNode:
    LoginView(continueNode: continueNode, isLoading: loginViewModel.isLoading,
              onNext: { Task { await loginViewModel.next(continueNode: continueNode) } })
default:
    LoginView(continueNode: nil, isLoading: loginViewModel.isLoading,
              onNext: { Task { await loginViewModel.start() } })
}

Callback Renderer (ContinueNodeView)

Callback lives in PingJourneyPlugin — import it alongside PingOrchestrate. Use any Callback in function signatures (Swift 6 existential syntax). Suppress the Next button when hasSelfAdvancingCallback (FIDO, Confirmation, Protect, SelectIdp, DeviceBinding callbacks advance themselves).

Use assets/ContinueNodeView.swift.journey.template as the full implementation reference. Always read references/journey-callbacks.md before writing any callback view code.

6 — Session Lifecycle

let user: User? = await AppJourney.shared.journey.journeyUser()

if let user = user {
    let result = await user.userinfo(cache: false)
    switch result {
    case .success(let info):
        info.forEach { key, value in print("\(key): \(String(describing: value))") }
    case .failure(let error):
        print(error.localizedDescription)
    }
}

_ = await AppJourney.shared.journey.signOff()

7 — App Entry Point Integration

Journey / DaVinci native flows — no onOpenURL needed:

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup { ContentView() }
    }
}

OIDC Web and social IdP callbacks — three required wiring points:

import PingBrowser

@main
struct MyApp: App {
    @Environment(\.scenePhase) private var scenePhase

    var body: some Scene {
        WindowGroup {
            ContentView()
                .onOpenURL { url in
                    OpenURLMonitor.shared.handleOpenURL(url)
                }
                .onChange(of: scenePhase) { _, phase in
                    if phase == .active,
                       BrowserLauncher.currentBrowser.isInProgress {
                        BrowserLauncher.currentBrowser.handleAppActivation()
                    }
                }
        }
    }
}

URL scheme registration — in scaffold projects using GENERATE_INFOPLIST_FILE = YES, use the Ruby snippet (Step D in assets/project-scaffolding.md). Do not hand-author Info.plist (causes Multiple commands produce Info.plist) or use xcconfig bracketed syntax (causes error: expected to find '=' in macro condition).

8 — OIDC Web Flow (OidcWebClient)

Choose OIDC Web when the IDP hosts the login page. Choose Journey native (Section 5) when you need credential fields in your app's SwiftUI views.

authorize() is async throws and returns Result<User, OidcError>. Two error channels — throws for network/OIDC discovery failures and Result.failure for authorization errors. Always handle both in a do/catch wrapping a switch result.

For the AppOidc singleton, OidcWebClientConfig API, OidcError cases, BrowserType trade-offs, OidcLoginViewModel, and branded OidcLoginView — read references/oidc-web-reference.md.

9 — DaVinci Flow

DaVinci uses collectors instead of callbacks. The @Observable ViewModel pattern is identical to Journey. Replace PingJourney.OidcModule with PingDavinci.OidcModule. DaVinci does not use serverUrl, realm, or cookieName.

hasSelfAdvancingCollector types: SubmitCollector, FlowCollector, FidoRegistrationCollector, FidoAuthenticationCollector, DeviceRegistrationCollector, DeviceAuthenticationCollector.

Critical: always set submitCollector.value = submitCollector.id and flowCollector.value = flowCollector.id before calling next() — these drive the actionKey and eventType in the POST body.

For the AppDaVinci singleton, full CollectorNodeView, individual collector views, DaVinciAuthenticatedView, and session lifecycle — read references/davinci-collectors.md.

10 — Swift 6 Notes

  • Mark ViewModels @MainActor — correct isolation for @Observable properties.
  • Use Task { } inside @MainActor code to bridge into async SDK calls.
  • Write any Callback / any Collector in function signatures — required by Swift 6 existential syntax.
  • Node is not Equatable. Track state transitions via Bool flags, not .onChange(of: node).
  • Always add import Foundation to @Observable classes that use UserDefaults.

11 — Ping Identity Visual Branding

Write assets/Theme.swift.template as-is to <AppName>/Theme.swift (substitute PLACEHOLDER_APP_NAME). Contains: PingPrimaryButtonStyle, PingHeaderView, PingTextField, PingSecureField, FidoIconView, ErrorMessageView, LoadingOverlay, ErrorView. Key tokens: Color.pingRed = #A31300, corner radius 15pt (buttons) / 8pt (fields), header padding 32pt, content padding 24pt.

Copy assets/Logo.imageset/ into Assets.xcassets/Logo.imageset/ and reference with Image("Logo"). Apply .tint(.pingRed) to any Form.

12 — Callback / Collector Tiers

Full tier breakdown with decision guide: references/callback-tiers.md

Summary: Basic (username/password/choice, no extra SPM) → Standard (adds FIDO, Protect, device binding, T&C, KBA, social IdP browser-based) → Full fat (adds native Apple/Google/Facebook social + ReCaptcha Enterprise). The wizard's callbackTier parameter maps directly to these tiers.

Common Pitfalls

See references/common-pitfalls.md — covers Swift 6 errors, OIDC redirect issues, DaVinci collector gotchas, SPM build errors, and simulator problems.

13 — Journey Export Analysis

When the user provides a Journey export JSON, run the full analysis in references/journey-export-analysis.md before writing any code (steps A1–A8: enumerate trees, map nodes→callbacks, inspect scripts, determine tier, extract config).