solibo-sdk

Usage Examples

This page provides practical examples of how to use the Solibo SDK in various scenarios.

Kotlin (Multiplatform / JVM / Android)

Full Application Setup (Android)

A typical integration in an Android application involves configuring the SDK with platform-specific implementations for Fingerprinter and PushTokenProvider. See the advanced setup guide for detailed implementation examples. Using a Dependency Injection framework like Koin is recommended.

ApiModule.kt

import no.solibo.oss.sdk.SoliboSDK
import no.solibo.oss.sdk.auth.Auth
import org.koin.dsl.module

val apiModule = module {
    single<SoliboSDK> {
        SoliboSDK.createMobile(
            userPoolId = "eu-west-1_...",
            clientId = "...",
            settings = get(),           // Multiplatform Settings (e.g., SharedPreferences)
            fingerprinter = get(),      // Platform implementation
            pushTokenProvider = get(),  // e.g., Firebase Messaging
            configure = {
                onRefreshFailure = {
                    // Global refresh failure handler (e.g., redirect to login)
                    get<Auth>().clearSession()
                }
                
                // Optional: custom Ktor HttpClient configuration
                httpClientConfig = {
                    install(Logging) {
                        level = LogLevel.INFO
                    }
                }
            }
        )
    }
    
    // Expose sub-APIs for easier injection
    single { get<SoliboSDK>().api }
    single { get<SoliboSDK>().auth }
}

MainApplication.kt

import android.app.Application
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin

class MainApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        startKoin {
            androidContext(this@MainApplication)
            modules(
                platformModule, // Module containing Fingerprinter and PushTokenProvider
                apiModule
            )
        }
    }
}

Full Application Setup (iOS)

For iOS applications using Swift, you can initialize the SDK through a shared KMP helper or directly. Below is an example of initializing the SDK in your App struct or AppDelegate.

iOSApp.swift

import SwiftUI
import Shared // Your shared KMP module

@main
struct SoliboHomeApp: App {
    init() {
        // Initialize Koin and the SDK with platform implementations
        HelperKt.doInitKoin(
            fingerprinter: iOSFingerprinter(),
            pushTokenProvider: iOSPushTokenProvider(),
            userPoolId: "eu-west-1_...",
            clientId: "..."
        )
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

The HelperKt and doInitKoin methods are typically defined in your shared KMP module to bridge between Swift and Kotlin. For platform-specific implementations, see the advanced setup guide.

Fetching User Profile

import no.solibo.oss.sdk.SoliboSDK
import kotlinx.coroutines.runBlocking

fun main() = runBlocking {
    val sdk = SoliboSDK.createMobile(
        userPoolId = "eu-west-1_...",
        clientId = "..."
    )
    // sdk.api.setInitialBaseUrl("https://home.solibo.no/api") // Optional, default is https://api.home.solibo.no
    
    try {
        val user = sdk.api.users.showUser()
        println("Hello, ${user.name?.given}!")
    } catch (e: Exception) {
        println("Error fetching user: ${e.message}")
    }
}

Updating a Board Member (Admin)

import no.solibo.oss.sdk.SoliboSDK
import no.solibo.oss.sdk.api.gen.models.UpdateBoardMemberCommand

suspend fun updateMember(sdk: SoliboSDK, boardId: String, memberId: String) {
    sdk.api.board.updateBoardMember(
        boardId = boardId,
        memberId = memberId,
        updateBoardMemberCommand = UpdateBoardMemberCommand(
            role = "Chairman"
        )
    )
}

Real-time Chat Subscription (Event Bus)

import no.solibo.oss.sdk.SoliboSDK
import no.solibo.oss.sdk.api.gen.models.ConversationMessagePayload

fun subscribeToMessages(sdk: SoliboSDK, conversationId: Long) {
    sdk.eventBus.onEvent { event ->
        val payload = event.payload
        if (payload is ConversationMessagePayload && payload.conversationId == conversationId) {
            println("New message: ${payload.message.text}")
        }
    }
}

MFA Management

import no.solibo.oss.sdk.SoliboSDK
import no.solibo.oss.sdk.api.gen.models.MfaType

suspend fun setupMfa(sdk: SoliboSDK) {
    // 1. Associate TOTP
    val association = sdk.auth.createTOTPMfa()
    println("Scan this secret in your app: ${association.secretCode}")
    
    // 2. Verify and enable
    sdk.auth.verifyCreateTOTPMfa(code = "123456")
    sdk.auth.createMfaPreference(MfaType.TOTP)
}

suspend fun handleMfaChallenge(sdk: SoliboSDK, code: String) {
    // The SDK automatically handles challenge type tracking
    val result = sdk.auth.answerMfaChallenge(code)
    if (result.tokens != null) {
        println("MFA successful!")
    }
}

Web (React / TypeScript)

Full Application Setup (React)

A typical integration in a React application involves configuring the SDK and wrapping the app with SoliboProvider. This should be placed inside your QueryClientProvider.

sdk.ts

import { CreateSdkArgs } from '@solibo/solibo-query';

export const sdkConfig: CreateSdkArgs = {
  // domain: 'home.solibo.no', // Optional, default is api.home.solibo.no
  cognitoUserPoolId: 'eu-west-1_...',
  
  // Fingerprinter for Cognito Advanced Security (optional)
  fingerprinter: {
    print: (username: string) => {
      // Access your platform's fingerprinting logic (e.g., AmazonCognitoAdvancedSecurityData)
      return (window as any).AmazonCognitoAdvancedSecurityData?.getData(
        username,
        'user-pool-id',
        'client-id'
      );
    },
  },

  // Global refresh failure handler (e.g., redirect to login)
  refreshFailureHandler: {
    onRefreshFailure: () => {
      localStorage.removeItem('user_session');
      window.location.href = '/auth/login';
    },
  },
};

App.tsx

import { StrictMode } from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { SoliboProvider } from '@solibo/solibo-query';
import { sdkConfig } from './sdk';

const queryClient = new QueryClient();

export default function App() {
  return (
    <StrictMode>
      <QueryClientProvider client={queryClient}>
        <SoliboProvider config={sdkConfig}>
          <YourRoutes />
        </SoliboProvider>
      </QueryClientProvider>
    </StrictMode>
  );
}

Using a Query Hook

The @solibo/solibo-query package provides hooks that integrate with TanStack React Query.

Fetching current user

import { useUser } from '@solibo/solibo-query'

function UserProfile() {
  const { data: user, isPending, isError } = useUser()

  if (isPending) return <div>Loading...</div>
  if (isError || !user) return <div>Error: Could not load user</div>

  return (
    <div>
      <h1>Welcome, {user.name.given}!</h1>
      <p>Email: {user.email}</p>
    </div>
  )
}

Fetching Board Members

import { useBoardMembers } from '@solibo/solibo-query'

function BoardMembersList({ boardId }: { boardId: bigint }) {
  const { data: members, isLoading, error } = useBoardMembers(boardId)

  if (isLoading) return <div>Loading...</div>
  if (error) return <div>Error: {error.message}</div>

  return (
    <ul>
      {members?.map(member => (
        <li key={member.id}>{member.name.given} {member.name.family}</li>
      ))}
    </ul>
  )
}

Using a Mutation Hook

import { useCreateBoardMember } from '@solibo/solibo-query'

function AddMemberForm({ boardId }: { boardId: bigint }) {
  const mutation = useCreateBoardMember()

  const handleSubmit = (event: React.FormEvent) => {
    event.preventDefault()
      
    // .mutateAsync for promise based mutations
    mutation.mutate({
      boardId,
      createBoardMemberCommand: {
        userId: '...',
        role: 'Member'
      }
    })
  }

  return (
    <form onSubmit={handleSubmit}>
      {/* form fields */}
      <button type="submit" disabled={mutation.isPending}>
        Add Member
      </button>
      {mutation.isError && <div>Error: {mutation.error.message}</div>}
    </form>
  )
}

MFA Management (React)

The SDK provides hooks for managing Multi-factor Authentication. The useAnswerMfaChallenge hook automatically tracks the current challenge type, so you don’t need to pass it explicitly if you just initiated an auth flow.

import { useState } from 'react'
import { 
  useRemoveMfa, 
  useCreateMfaPreference, 
  useAnswerMfaChallenge,
  useInitiateAuth 
} from '@solibo/solibo-query'
import { MfaType, AuthChallengeType } from '@solibo/solibo-sdk'

function Login() {
  const loginMutation = useInitiateAuth()
  const verifyMutation = useAnswerMfaChallenge()
  const [mfaChallenge, setMfaChallenge] = useState<AuthChallengeType | null>(null)
  const [code, setCode] = useState('')

  const handleLogin = async (e: React.FormEvent) => {
    e.preventDefault()
    const result = await loginMutation.mutateAsync({
      creds: { login: '...', pwd: '...' },
      fcmDevice: 'WEB'
    })

    if (result.challenge) {
      setMfaChallenge(result.challenge.type)
    }
  }

  const handleVerify = () => {
    // No need to pass challengeType! The SDK tracks it.
    verifyMutation.mutate({ code })
  }

  if (mfaChallenge) {
    return (
      <div>
        <p>Enter your {mfaChallenge} code:</p>
        <input value={code} onChange={e => setCode(e.target.value)} />
        <button onClick={handleVerify}>Verify</button>
      </div>
    )
  }

  return (
    <form onSubmit={handleLogin}>
      {/* login fields */}
      <button type="submit">Login</button>
    </form>
  )
}

function MfaSettings() {
  const removeMfa = useRemoveMfa()
  const setPreference = useCreateMfaPreference()

  return (
    <div>
      <button onClick={() => removeMfa.mutate()}>Disable MFA</button>
      <button onClick={() => setPreference.mutate({ mfaType: MfaType.SMS })}>
        Use SMS MFA
      </button>
    </div>
  )
}

function TotpSetup() {
  const startTotp = useCreateTOTPMfa()
  const verifyTotp = useVerifyCreateTOTPMfa()
  const [secret, setSecret] = useState<string | null>(null)
  const [code, setCode] = useState('')

  const handleStart = async () => {
    const res = await startTotp.mutateAsync()
    setSecret(res.secretCode!)
  }

  const handleVerify = () => {
    verifyTotp.mutate({ code, deviceName: 'My Phone' })
  }

  if (secret) {
    return (
      <div>
        <p>Your TOTP Secret: {secret}</p>
        <input value={code} onChange={e => setCode(e.target.value)} />
        <button onClick={handleVerify}>Verify & Enable</button>
      </div>
    )
  }

  return <button onClick={handleStart}>Enroll in TOTP</button>
}

Real-time Notifications (Event Bus)

import { useEffect } from 'react'
import { useSoliboApi } from '@solibo/solibo-query'

function NotificationListener() {
  const { eventBus } = useSoliboApi()

  useEffect(() => {
    // onEvent returns an unsubscribe function
    const unsubscribe = eventBus.onEvent(({ payload }) => {
      // Discriminator-based type check
      if (payload.type === 'solibo.common.infrastructure.websockets.payload.ConversationMessagePayload') {
        alert('New message received!')
      }
    })

    return () => unsubscribe()
  }, [eventBus])

  return <div>Monitoring for live updates...</div>
}

Machine-to-Machine (M2M)

For backend services, scripts, or other non-interactive clients, the SDK supports M2M authentication using OAuth 2.0 Client Credentials flow.

Setup (Kotlin)

import no.solibo.oss.sdk.SoliboSDK
import kotlinx.coroutines.runBlocking

fun main() = runBlocking {
    val sdk = SoliboSDK.createM2M(
        clientId = "your-client-id",
        clientSecret = "your-client-secret",
        scopes = listOf("api.read", "api.write")
    )
    
    val user = sdk.api.users.showUser()
    println("Service acting as: ${user.name?.given}")
}

Setup (TypeScript)

import { SoliboSDKJsBuilder } from '@solibo/solibo-query';

async function run() {
  const sdk = await SoliboSDKJsBuilder.createM2M(
    'your-client-id',
    'your-client-secret',
    ['api.read', 'api.write']
  );
  
  const user = await sdk.api.users.showUser();
  console.log(`Service acting as: ${user.name?.given}`);
}