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
HelperKtanddoInitKoinmethods 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!")
}
}
Uploading a Document with Progress (Kotlin)
Use sdk.api.documents.uploadDocument(...) when you want the shared KMP upload flow, including optional progress updates:
import no.solibo.oss.sdk.SoliboSDK
import no.solibo.oss.sdk.api.UploadDocumentRequest
import no.solibo.oss.sdk.api.UploadProgress
import no.solibo.oss.sdk.api.gen.models.DocumentType
suspend fun uploadMeetingMinutes(
sdk: SoliboSDK,
companyId: Long,
fileName: String,
bytes: ByteArray,
) {
var lastPercent = -1
val document =
sdk.api.documents.uploadDocument(
request = UploadDocumentRequest(
companyId = companyId,
documentType = DocumentType.MEETING,
fileName = fileName,
bytes = bytes,
contentType = "application/pdf",
),
) { progress: UploadProgress ->
val percent = (progress.fraction * 100).toInt()
if (percent != lastPercent) {
lastPercent = percent
println("Upload progress: $percent% (${progress.loaded}/${progress.total})")
}
}
println("Uploaded document ${document.id} -> ${document.fileName}")
}
Creating a Post with Integrated Documents (Kotlin)
val result = sdk.api.integratedDocuments.createHomepagePost(
companyId = companyId,
command = CreatePostCommand(
title = "Welcome",
content = inlineDocumentImageSlot("entrance", "Building entrance"),
),
image = DocumentInput.Upload("cover.jpg", coverBytes, "image/jpeg"),
attachments = listOf(
DocumentInput.Existing(sourceDocumentId = houseRulesDocumentId),
),
inlineDocuments = mapOf(
"entrance" to DocumentInput.Upload("entrance.jpg", entranceBytes, "image/jpeg"),
),
)
println("Created post ${result.resource.id}")
The SDK requests tickets and uploads new binaries concurrently. Existing inputs do not upload anything; the final backend mutation creates contextual references for them.
Creating a Newsletter with an Inline Image (Kotlin)
val result = sdk.api.integratedDocuments.createNewsletter(
companyId = companyId,
command = CreateNewsletterCommand(
subject = "Summer update",
plainText = "See the renovated entrance.",
htmlText = "<p>See the renovated entrance:</p>" +
inlineDocumentImageSlot(
"entrance",
"Renovated entrance",
InlineDocumentImageOptions(
width = 600,
className = "newsletter-hero",
style = "display: block; max-width: 100%; height: auto;",
),
),
),
inlineDocuments = mapOf(
"entrance" to DocumentInput.Upload(
"entrance.jpg",
entranceBytes,
"image/jpeg",
),
),
)
The persisted htmlText contains data-solibo-document-id, not a signed URL. API reads expose renderedHtmlText with an authenticated short-lived URL, while sent email uses a revocable email capability URL.
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-react';
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-react';
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 @solibo/solibo-react Hooks
The @solibo/solibo-react package provides React hooks and SoliboProvider. Import query factories/keys from @solibo/solibo-query, and import SDK clients/models from @solibo/solibo-sdk.
Fetching current user
import { useUser } from '@solibo/solibo-react'
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-react'
function BoardMembersList({ companyId }: { companyId: bigint }) {
const { data, isLoading, error } = useBoardMembers({ companyId })
const members = data?.pages.flatMap(page => page.items) ?? []
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-react'
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>
)
}
Document Upload and Download (React / TypeScript)
For React applications, the document wrappers live in @solibo/solibo-react. The matching framework-agnostic option factories live in @solibo/solibo-query.
Creating a post with integrated documents
import {
inlineDocumentImageSlot,
} from '@solibo/solibo-sdk'
import { useCreateHomepagePostWithDocuments } from '@solibo/solibo-react'
function CreatePost({ companyId, entrance }: { companyId: number; entrance: File }) {
const createPost = useCreateHomepagePostWithDocuments()
return (
<button
onClick={() =>
createPost.mutate({
companyId,
command: {
title: 'Welcome',
content: inlineDocumentImageSlot('entrance', 'Building entrance'),
},
attachments: [{ sourceDocumentId: 9183 }],
inlineDocuments: {
entrance: { file: entrance },
},
onUploadProgress: ({ fileName, fraction }) => {
console.log(fileName, Math.round(fraction * 100))
},
})
}
>
Publish
</button>
)
}
Creating a newsletter with an inline image
import {
InlineDocumentImageOptions,
inlineDocumentImageSlot,
} from '@solibo/solibo-sdk'
import { useCreateNewsletterWithDocuments } from '@solibo/solibo-react'
function CreateNewsletter({ companyId, entrance }: { companyId: number; entrance: File }) {
const createNewsletter = useCreateNewsletterWithDocuments()
return (
<button onClick={() => createNewsletter.mutate({
companyId,
command: {
subject: 'Summer update',
plainText: 'See the renovated entrance.',
htmlText: '<p>See the renovated entrance:</p>' +
inlineDocumentImageSlot(
'entrance',
'Renovated entrance',
new InlineDocumentImageOptions({
width: 600,
className: 'newsletter-hero',
style: 'display: block; max-width: 100%; height: auto;',
}),
),
},
inlineDocuments: {
entrance: { file: entrance },
},
})}>
Create newsletter
</button>
)
}
Uploading a private company document with the SDK facade (TypeScript)
When you want the shared upload flow without TanStack Query, call the facade’s sdk.api.documents.uploadDocument(...) helper and pass a progress callback:
import {
DocumentType,
SoliboClient,
} from '@solibo/solibo-sdk'
async function uploadMeetingMinutes(
sdk: SoliboClient,
companyId: number,
file: File,
) {
const bytes = new Int8Array(await file.arrayBuffer())
const document = await sdk.api.documents.uploadDocument({
request: {
companyId,
documentType: DocumentType.Meeting,
fileName: file.name,
bytes,
contentType: file.type || undefined,
},
onUploadProgress: (progress) => {
console.log(
`Upload ${Math.round(progress.fraction * 100)}% (${progress.loaded}/${progress.total})`,
)
},
})
console.log(`Uploaded document ${document.id}`)
return document
}
Uploading a private company document with @solibo/solibo-query
Use @solibo/solibo-query when you want to keep your own SDK wiring but reuse the shared TanStack mutation options:
import { useState } from 'react'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { DocumentType, SoliboClient } from '@solibo/solibo-sdk'
import { uploadDocumentMutationOptions } from '@solibo/solibo-query'
function UploadButton(props: {
sdk: SoliboClient
companyId: number
file: File
}) {
const queryClient = useQueryClient()
const [progress, setProgress] = useState(0)
const uploadDocument = useMutation(
uploadDocumentMutationOptions(props.sdk, queryClient),
)
const handleUpload = async () => {
await uploadDocument.mutateAsync({
companyId: props.companyId,
documentType: DocumentType.Other,
file: props.file,
onUploadProgress: ({ fraction }) => {
setProgress(Math.round(fraction * 100))
},
})
}
return (
<>
<button onClick={handleUpload}>Upload document</button>
<progress max={100} value={progress} />
</>
)
}
Uploading a private company document with @solibo/solibo-react
import { useState } from 'react'
import { useUploadDocument } from '@solibo/solibo-react'
import { DocumentType } from '@solibo/solibo-sdk'
function UploadButton({ companyId, file }: { companyId: number; file: File }) {
const uploadDocument = useUploadDocument()
const [progress, setProgress] = useState(0)
const handleUpload = async () => {
await uploadDocument.mutateAsync({
companyId,
documentType: DocumentType.Other,
file,
onUploadProgress: ({ fraction }) => setProgress(Math.round(fraction * 100)),
})
}
return (
<>
<button onClick={handleUpload}>Upload document</button>
<progress max={100} value={progress} />
</>
)
}
The upload wrappers automatically handle the backend’s signed upload flow for you. You pass a File, and the wrapper reuses the shared KMP DocumentsApi upload helpers to create the document, upload the bytes, verify the upload, and clean up on failure.
Uploading to a public conversation with @solibo/solibo-react
import { useState } from 'react'
import { usePublicUploadDocumentToConversation } from '@solibo/solibo-react'
function PublicConversationUploadButton(props: {
companySlug: string
conversationId: number
file: File
}) {
const uploadDocument = usePublicUploadDocumentToConversation()
const [progress, setProgress] = useState(0)
const handleUpload = async () => {
await uploadDocument.mutateAsync({
companySlug: props.companySlug,
conversationId: props.conversationId,
file: props.file,
onUploadProgress: ({ fraction }) => {
setProgress(Math.round(fraction * 100))
},
})
}
return (
<>
<button onClick={handleUpload}>Upload public document</button>
<progress max={100} value={progress} />
</>
)
}
For multiple files, use useMultiUploadPublicDocumentToConversation(). The SDK creates one internal upload group id for the batch so the backend can render one logical upload event without making the group id a client concern.
Using the low-level signed-upload helpers (TypeScript)
Use raw interop only when you need to drive a signed-upload endpoint manually. For the normal private document flow, prefer sdk.api.documents.uploadDocument(...), the Query mutation factory, or the React wrapper above.
import { DocumentType, SoliboClient, UploadBinaryRequest } from '@solibo/solibo-sdk'
async function uploadWithSignedEndpoint(
sdk: SoliboClient,
companyId: bigint,
file: File,
) {
const uploadRequest = new UploadBinaryRequest({
fileName: file.name,
bytes: new Int8Array(await file.arrayBuffer()),
contentType: file.type || undefined,
verifyUpload: true,
})
const requestHeaders = new Map(
sdk.raw.api.documents.createUploadHeaders(uploadRequest).asJsReadonlyMapView(),
)
sdk.raw.api.documents.setNextRequestHeaders(requestHeaders)
const response = await sdk.raw.api.documents.createDocument(
companyId,
DocumentType.Other,
undefined,
false,
)
await sdk.api.documents.completeSignedUpload({
headers: response.headers,
request: uploadRequest,
onUploadProgress: ({ fraction, loaded, total }) => {
console.log(
`Upload ${Math.round(fraction * 100)}% (${loaded}/${total})`,
)
},
})
return await response.body()
}
Uploading a document linked to another resource
import { useUploadDocumentBelongsTo } from '@solibo/solibo-react'
import { DocumentType } from '@solibo/solibo-sdk'
function UploadMeetingAttachment(props: {
companyId: number
meetingId: number
file: File
}) {
const uploadDocument = useUploadDocumentBelongsTo()
const handleUpload = async () => {
await uploadDocument.mutateAsync({
companyId: props.companyId,
documentType: DocumentType.Meeting,
belongsToId: props.meetingId,
file: props.file,
})
}
return <button onClick={handleUpload}>Upload attachment</button>
}
Creating a directory
Use useCreateDocumentDirectory() for folders instead of the file upload wrappers:
import { useCreateDocumentDirectory } from '@solibo/solibo-react'
import { DocumentType } from '@solibo/solibo-sdk'
function CreateFolder({ companyId }: { companyId: number }) {
const createDirectory = useCreateDocumentDirectory()
const handleCreate = async () => {
await createDirectory.mutateAsync({
companyId,
documentType: DocumentType.Board,
file: new File(['directory'], 'Board Documents'),
})
}
return <button onClick={handleCreate}>Create folder</button>
}
Resolving a private document download URL
import { useGetDocumentURL } from '@solibo/solibo-react'
function DownloadLink(props: { companyId: number; documentId: number }) {
const { data, isPending } = useGetDocumentURL({
companyId: props.companyId,
documentId: props.documentId,
})
if (isPending) return <span>Loading...</span>
if (!data?.url) return <span>Missing URL</span>
return (
<a href={data.url} target="_blank" rel="noreferrer">
Open document
</a>
)
}
useGetDocumentURL() returns a UrlWrapper body with a signed url. It does not auto-redirect, which lets your app decide whether to open, preview, or download the file.
Resolving a public document URL
import { usePublicGetDocumentURL } from '@solibo/solibo-react'
function PublicDocumentLink({ documentId }: { documentId: number }) {
const { getDocumentURL } = usePublicGetDocumentURL({
companyIdOverride: 42,
})
const handleOpen = async () => {
const { url } = await getDocumentURL(documentId)
window.open(url, '_blank', 'noopener')
}
return <button onClick={handleOpen}>Open public document</button>
}
For public conversation documents, call getDocumentURL(conversationId, documentId) and provide slug in the hook params. Any size or fields options you pass to usePublicGetDocumentURL(...) are forwarded for both public company and public conversation document URLs.
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,
useCreateTOTPMfa,
useCreateMfaPreference,
useAnswerMfaChallenge,
useInitiateAuth,
useVerifyCreateTOTPMfa,
} from '@solibo/solibo-react'
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>
}
Buildings and Sections
Kotlin: update an import draft with buildings and detached sections
import no.solibo.oss.sdk.SoliboSDK
import no.solibo.oss.sdk.api.gen.models.CompanyStatus
import no.solibo.oss.sdk.api.gen.models.CreateBuilding
import no.solibo.oss.sdk.api.gen.models.CreateBuildingFloor
import no.solibo.oss.sdk.api.gen.models.CreateSection
import no.solibo.oss.sdk.api.gen.models.SectionFractions
import no.solibo.oss.sdk.api.gen.models.SectionType
import no.solibo.oss.sdk.api.gen.models.UpdateKartverketResultCommand
suspend fun enrichImportDraft(sdk: SoliboSDK, draftId: Long) {
val draft = sdk.api.companies.showImportDraft(draftId).body()
val updated =
sdk.api.companies.updateImportDraft(
draftId,
UpdateKartverketResultCommand(
company =
draft.result.company.copy(
managed = true,
nationality = 47L,
status = CompanyStatus.ACTIVE,
buildings =
listOf(
CreateBuilding(
name = "Bygg A",
kartverketId = "building-1",
constructionYear = 1999L,
floors =
listOf(
CreateBuildingFloor(
floorNumber = 2L,
floorTypeCode = "H",
kartverketId = "floor-1",
),
),
),
),
detachedSections =
listOf(
CreateSection(
classification = SectionType.BOLIG,
buildingId = 1L,
floorId = 1L,
hNr = "H0201",
identifier = "A-201",
fractions =
SectionFractions(
owner = "2/4",
sectioning = "2/4",
inLoanOne = "1/4",
inLoanTwo = "1/4",
),
roomCount = 3L,
unitType = "Apartment",
unitTypeCode = "APT",
),
),
),
),
).body()
println("Draft now has ${updated.company.buildings?.size ?: 0} building(s)")
}
CreateCompany is currently exposed through the import-draft workflow rather than @solibo/solibo-query / @solibo/solibo-react wrappers.
TypeScript SDK: read the company summary and update a section
import {
SectionType,
createSoliboClient,
} from '@solibo/solibo-sdk'
async function syncSectionSummary() {
const sdk = createSoliboClient({
auth: {
kind: 'browser',
userPoolId: 'eu-west-1_...',
clientId: '...',
},
})
const detailed = await sdk.api.companies.showCompanyDetailed({ companyId: 1 })
console.log(detailed.sections?.count, detailed.sections?.businessCount)
const updated = await sdk.api.sections.updateSection({
companyId: 1,
sectionId: 1,
input: {
companyId: 1n,
sectionType: SectionType.Bolig,
hnr: 'H0301',
identifier: 'A-301',
ownerFraction: '2/4',
sectioningFraction: '2/4',
wealthFraction: '2/4',
},
})
console.log(updated.hNr, updated.identifier)
}
@solibo/solibo-query: build query and mutation options
import { QueryClient } from '@tanstack/query-core'
import { SectionType, SoliboClient } from '@solibo/solibo-sdk'
import {
sectionsQueryOptions,
updateSectionMutationOptions,
} from '@solibo/solibo-query'
function buildSectionOptions(sdk: SoliboClient) {
const queryClient = new QueryClient()
const sections = sectionsQueryOptions(sdk, {
companyId: 1n,
fields: 'id,identifier,hNr,building,floor,ownerFraction',
})
const updateSection = updateSectionMutationOptions(sdk, queryClient)
return {
sections,
updateSection,
updatePayload: {
companyId: 1n,
sectionId: 1n,
cmd: {
companyId: 1n,
sectionType: SectionType.Bolig,
hnr: 'H0301',
identifier: 'A-301',
ownerFraction: '2/4',
sectioningFraction: '2/4',
wealthFraction: '2/4',
},
},
}
}
updateSectionMutationOptions(...), createSectionMutationOptions(...), createSubSectionMutationOptions(...), and deleteSectionMutationOptions(...) now also invalidate companyDetailed(...) so React apps refresh CompanyDetailed.sections after section changes.
@solibo/solibo-react: show building metadata and update a section
import {
useCompanyDetailed,
useSections,
useUpdateSection,
} from '@solibo/solibo-react'
import { SectionType } from '@solibo/solibo-sdk'
function SectionsPanel({ companyId }: { companyId: bigint }) {
const company = useCompanyDetailed({ id: companyId })
const sections = useSections({
companyId,
fields: 'id,identifier,hNr,building,floor,ownerFraction',
})
const updateSection = useUpdateSection()
const items = sections.data?.pages.flatMap(page => page.items) ?? []
return (
<div>
<p>Total sections: {String(company.data?.sections?.count ?? 0n)}</p>
{items.map(section => (
<button
key={String(section.id)}
onClick={() =>
updateSection.mutate({
companyId,
sectionId: section.id,
cmd: {
companyId,
sectionType: SectionType.Bolig,
hnr: 'H0301',
identifier: 'A-301',
ownerFraction: '2/4',
sectioningFraction: '2/4',
wealthFraction: '2/4',
},
})
}
>
{section.identifier} ({section.building?.name} / {section.floor?.floorTypeCode})
</button>
))}
</div>
)
}
Real-time Notifications (Event Bus)
import { useEffect } from 'react'
import { useSoliboApi } from '@solibo/solibo-react'
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"
)
val user = sdk.api.users.showUser()
println("Service acting as: ${user.name?.given}")
}
Setup (TypeScript)
import { createSoliboClient } from '@solibo/solibo-sdk';
async function run() {
const sdk = createSoliboClient({
auth: {
kind: 'm2m',
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
},
});
const user = await sdk.api.users.showUser({});
console.log(`Service acting as: ${user.name?.given}`);
}
M2M clients are normally scoped server-side, so the SDK omits scopes from the token request unless you explicitly provide them.