562 lines
18 KiB
Markdown
562 lines
18 KiB
Markdown
# IronServices.Maui
|
|
|
|
MAUI controls and services for IronServices (IronLicensing, IronNotify, IronTelemetry).
|
|
|
|
[](https://www.nuget.org/packages/IronServices.Maui/)
|
|
[](https://opensource.org/licenses/MIT)
|
|
|
|
## Installation
|
|
|
|
```xml
|
|
<PackageReference Include="IronServices.Maui" Version="1.0.0" />
|
|
```
|
|
|
|
## Quick Start
|
|
|
|
### 1. Register Services
|
|
|
|
```csharp
|
|
// MauiProgram.cs
|
|
builder.Services.AddIronServices();
|
|
|
|
// Or with custom URLs
|
|
builder.Services.AddIronServices(options =>
|
|
{
|
|
options.LicensingUrl = "https://ironlicensing.com";
|
|
options.NotifyUrl = "https://ironnotify.com";
|
|
options.TelemetryUrl = "https://irontelemetry.com";
|
|
});
|
|
```
|
|
|
|
### 2. Use Controls
|
|
|
|
```xaml
|
|
<ContentPage xmlns:iron="clr-namespace:IronServices.Maui.Controls;assembly=IronServices.Maui">
|
|
<iron:LoginView Client="{Binding IronClient}" LoginSuccess="OnLoginSuccess" />
|
|
</ContentPage>
|
|
```
|
|
|
|
---
|
|
|
|
## Controls
|
|
|
|
| Control | Type | Description |
|
|
|---------|------|-------------|
|
|
| [`LoginView`](#loginview) | ContentView | Drop-in login form |
|
|
| [`LicenseActivationView`](#licenseactivationview) | ContentView | License key entry and activation |
|
|
| [`AppLogView`](#applogview) | ContentPage | Exception and telemetry log viewer |
|
|
| [`UserJourneyView`](#userjourneyview) | ContentPage | User journey visualization with Timeline/Tree/Flow modes |
|
|
|
|
---
|
|
|
|
### LoginView
|
|
|
|
A drop-in login form for IronServices authentication.
|
|
|
|
**Type:** `ContentView` (embeddable)
|
|
|
|
#### Properties
|
|
|
|
| Property | Type | Default | Description |
|
|
|----------|------|---------|-------------|
|
|
| `Client` | `IronServicesClient` | `null` | **Required.** The client instance for authentication. |
|
|
| `Title` | `string` | `"Sign In"` | Header text above the form. |
|
|
| `Subtitle` | `string` | `"Enter your credentials..."` | Subheader text. |
|
|
| `Logo` | `ImageSource` | `null` | Optional logo image. |
|
|
| `ShowRegisterLink` | `bool` | `true` | Show "Sign Up" link. |
|
|
| `ShowForgotPasswordLink` | `bool` | `true` | Show "Forgot Password?" link. |
|
|
|
|
#### Events
|
|
|
|
| Event | Args | Description |
|
|
|-------|------|-------------|
|
|
| `LoginSuccess` | `LoginSuccessEventArgs` | Fired on successful login. Contains `UserId`, `Email`, `DisplayName`. |
|
|
| `LoginFailed` | `LoginFailedEventArgs` | Fired on failed login. Contains `Error` message. |
|
|
| `RegisterRequested` | `EventArgs` | User tapped "Sign Up" link. |
|
|
| `ForgotPasswordRequested` | `EventArgs` | User tapped "Forgot Password?" link. |
|
|
|
|
#### Example
|
|
|
|
```csharp
|
|
// XAML
|
|
<iron:LoginView x:Name="LoginView"
|
|
Client="{Binding IronClient}"
|
|
Title="Welcome Back"
|
|
Logo="logo.png"
|
|
ShowRegisterLink="True"
|
|
LoginSuccess="OnLoginSuccess"
|
|
RegisterRequested="OnRegisterRequested" />
|
|
|
|
// Code-behind
|
|
private async void OnLoginSuccess(object sender, LoginSuccessEventArgs e)
|
|
{
|
|
await DisplayAlert("Success", $"Welcome, {e.DisplayName}!", "OK");
|
|
await Shell.Current.GoToAsync("//main");
|
|
}
|
|
|
|
private async void OnRegisterRequested(object sender, EventArgs e)
|
|
{
|
|
await Shell.Current.GoToAsync("//register");
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### LicenseActivationView
|
|
|
|
A license key entry and activation form.
|
|
|
|
**Type:** `ContentView` (embeddable)
|
|
|
|
#### Properties
|
|
|
|
| Property | Type | Default | Description |
|
|
|----------|------|---------|-------------|
|
|
| `LicenseManager` | `ILicenseManager` | `null` | **Required.** The license manager instance. |
|
|
| `Title` | `string` | `"Activate License"` | Header text. |
|
|
| `Subtitle` | `string` | `"Enter your license key..."` | Subheader text. |
|
|
| `LicenseKey` | `string` | `null` | Pre-filled license key (two-way binding). |
|
|
| `ShowHelpLink` | `bool` | `true` | Show help link below form. |
|
|
| `HelpUrl` | `string` | `"https://..."` | URL opened when help tapped. |
|
|
|
|
#### Events
|
|
|
|
| Event | Args | Description |
|
|
|-------|------|-------------|
|
|
| `LicenseActivated` | `LicenseActivatedEventArgs` | Activation succeeded. Contains `License` info. |
|
|
| `ActivationFailed` | `LicenseActivationFailedEventArgs` | Activation failed. Contains `Error` message. |
|
|
| `HelpRequested` | `EventArgs` | User tapped help link. |
|
|
|
|
#### Methods
|
|
|
|
| Method | Returns | Description |
|
|
|--------|---------|-------------|
|
|
| `ValidateAsync()` | `Task<bool>` | Validate the entered license key without activating. |
|
|
|
|
#### Example
|
|
|
|
```csharp
|
|
// XAML
|
|
<iron:LicenseActivationView x:Name="ActivationView"
|
|
LicenseManager="{Binding LicenseManager}"
|
|
LicenseActivated="OnLicenseActivated" />
|
|
|
|
// Code-behind
|
|
private async void OnLicenseActivated(object sender, LicenseActivatedEventArgs e)
|
|
{
|
|
var license = e.License;
|
|
await DisplayAlert("Activated",
|
|
$"License: {license.Tier}\nExpires: {license.ExpiresAt:d}", "OK");
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### AppLogView
|
|
|
|
A full-page log viewer for exceptions and telemetry events. Shows queued items from `TelemetryClient.OfflineQueue`.
|
|
|
|
**Type:** `ContentPage` (standalone page, use with NavigationPage)
|
|
|
|
#### Properties
|
|
|
|
| Property | Type | Default | Description |
|
|
|----------|------|---------|-------------|
|
|
| `TelemetryClient` | `TelemetryClient` | `null` | Client to pull queued logs from. Auto-refreshes on set. |
|
|
| `Title` | `string` | `"App Logs"` | Page title (shown in navigation bar). |
|
|
| `ShowShareButton` | `bool` | `true` | Enable Share toolbar button. |
|
|
| `ShowClearButton` | `bool` | `true` | Enable Clear toolbar button. |
|
|
| `EnableLiveUpdates` | `bool` | `false` | Auto-refresh when new items are added (polls every 2s). |
|
|
|
|
#### Events
|
|
|
|
| Event | Args | Description |
|
|
|-------|------|-------------|
|
|
| `LogsCleared` | `EventArgs` | User cleared the log list. |
|
|
| `LogsShared` | `EventArgs` | User shared the logs. |
|
|
| `LogSelected` | `LogItem` | User selected a log entry. |
|
|
| `LogsRefreshed` | `EventArgs` | Log list was refreshed. |
|
|
|
|
#### Methods
|
|
|
|
| Method | Returns | Description |
|
|
|--------|---------|-------------|
|
|
| `RefreshLogs()` | `void` | Reload logs from TelemetryClient queue. |
|
|
| `AddLog(type, title, message, stackTrace?)` | `void` | Add a manual log entry. |
|
|
| `AddException(Exception)` | `void` | Add an exception as a log entry. |
|
|
| `ClearLogs()` | `void` | Clear displayed logs (not the queue). |
|
|
| `ClearAllLogs()` | `void` | Clear displayed logs AND the offline queue. |
|
|
| `ExportLogs()` | `string` | Export all logs as formatted text. |
|
|
| `GetLogs()` | `IReadOnlyList<LogItem>` | Get current log items. |
|
|
| `LogCount` | `int` | Number of log items. |
|
|
|
|
#### LogItem Properties
|
|
|
|
| Property | Type | Description |
|
|
|----------|------|-------------|
|
|
| `Timestamp` | `DateTime` | UTC timestamp. |
|
|
| `Type` | `string` | `"exception"`, `"message"`, `"journey_start"`, `"step_start"`, etc. |
|
|
| `Title` | `string` | Exception type name or message title. |
|
|
| `Message` | `string` | Log message. |
|
|
| `StackTrace` | `string?` | Stack trace for exceptions. |
|
|
| `JourneyId` | `string?` | Associated user journey ID. |
|
|
| `UserId` | `string?` | Associated user ID. |
|
|
| `TypeDisplay` | `string` | Short type label: `"ERROR"`, `"MSG"`, `"START"`, etc. |
|
|
| `TypeColor` | `Color` | Color for type badge. |
|
|
| `TimestampDisplay` | `string` | Formatted local time (HH:mm:ss). |
|
|
|
|
#### Example
|
|
|
|
```csharp
|
|
// Navigate to log view
|
|
await Navigation.PushAsync(new AppLogView
|
|
{
|
|
TelemetryClient = _telemetryClient,
|
|
Title = "Error Logs",
|
|
EnableLiveUpdates = true
|
|
});
|
|
|
|
// With event handling
|
|
var logView = new AppLogView { TelemetryClient = _telemetryClient };
|
|
logView.LogsCleared += (s, e) => Debug.WriteLine("Logs cleared");
|
|
logView.LogSelected += (s, item) => Debug.WriteLine($"Selected: {item.Title}");
|
|
await Navigation.PushAsync(logView);
|
|
|
|
// Manual log entries (without TelemetryClient)
|
|
var logView = new AppLogView();
|
|
logView.AddException(ex);
|
|
logView.AddLog("warning", "Network Issue", "Connection timeout after 30s");
|
|
```
|
|
|
|
#### Toolbar
|
|
|
|
- **Refresh** (in header): Reloads from queue.
|
|
- **Share** (Primary): Exports logs as text and opens system share sheet.
|
|
- **Clear** (Secondary/overflow): Clears displayed logs.
|
|
|
|
---
|
|
|
|
### UserJourneyView
|
|
|
|
A full-page user journey viewer with multiple visualization modes. Reconstructs journeys from `TelemetryClient` log items and displays steps, breadcrumbs, exceptions, and metadata.
|
|
|
|
**Type:** `ContentPage` (standalone page, use with NavigationPage)
|
|
|
|
#### View Modes
|
|
|
|
| Mode | Description |
|
|
|------|-------------|
|
|
| `Timeline` | Vertical list with nested steps shown with indentation (default). |
|
|
| `Tree` | Expandable tree hierarchy with collapsible journey/step nodes. |
|
|
| `Flow` | Horizontal flowchart-style diagram showing step progression. |
|
|
|
|
#### Properties
|
|
|
|
| Property | Type | Default | Description |
|
|
|----------|------|---------|-------------|
|
|
| `TelemetryClient` | `TelemetryClient` | `null` | Client to pull journey data from. Auto-refreshes on set. |
|
|
| `Title` | `string` | `"User Journeys"` | Page title. |
|
|
| `ViewMode` | `JourneyViewMode` | `Timeline` | Current visualization mode. |
|
|
| `EnableLiveUpdates` | `bool` | `false` | Auto-refresh when new items are added (polls every 2s). |
|
|
| `ShowShareButton` | `bool` | `true` | Enable Share toolbar button. |
|
|
| `ShowClearButton` | `bool` | `true` | Enable Clear toolbar button. |
|
|
| `UserIdFilter` | `string?` | `null` | Filter to show only journeys for a specific user. |
|
|
|
|
#### Events
|
|
|
|
| Event | Args | Description |
|
|
|-------|------|-------------|
|
|
| `JourneySelected` | `JourneyItem` | User selected a journey from the list. |
|
|
| `StepSelected` | `StepItem` | User selected a step in the detail panel. |
|
|
| `JourneysRefreshed` | `EventArgs` | Journey list was refreshed. |
|
|
| `JourneysCleared` | `EventArgs` | Journeys were cleared. |
|
|
| `JourneysShared` | `EventArgs` | Journeys were exported/shared. |
|
|
|
|
#### Methods
|
|
|
|
| Method | Returns | Description |
|
|
|--------|---------|-------------|
|
|
| `RefreshJourneys()` | `void` | Reload journeys from TelemetryClient. |
|
|
| `ClearJourneys()` | `void` | Clear displayed journeys. |
|
|
| `ClearAllJourneys()` | `void` | Clear displayed journeys AND the TelemetryClient queue. |
|
|
| `ExportJourneys()` | `string` | Export all journeys as formatted text. |
|
|
| `GetJourneys()` | `IReadOnlyList<JourneyItem>` | Get current journey items. |
|
|
| `JourneyCount` | `int` | Number of journeys. |
|
|
|
|
#### JourneyItem Properties
|
|
|
|
| Property | Type | Description |
|
|
|----------|------|-------------|
|
|
| `JourneyId` | `string` | Unique journey identifier. |
|
|
| `Name` | `string` | Journey name (e.g., "Checkout Flow"). |
|
|
| `UserId` | `string?` | Associated user ID. |
|
|
| `UserEmail` | `string?` | Associated user email. |
|
|
| `Status` | `JourneyDisplayStatus` | `InProgress`, `Completed`, `Failed`, `Abandoned`. |
|
|
| `StartTime` | `DateTime` | When the journey started. |
|
|
| `EndTime` | `DateTime?` | When the journey ended (null if in progress). |
|
|
| `DurationMs` | `double?` | Duration in milliseconds. |
|
|
| `DurationDisplay` | `string` | Formatted duration: `"125ms"`, `"2.3s"`, `"1.5m"`. |
|
|
| `TimeAgo` | `string` | Relative time: `"just now"`, `"5m ago"`, `"2h ago"`. |
|
|
| `StatusDisplay` | `string` | Human-readable status text. |
|
|
| `StatusColor` | `Color` | Color for status badge (Blue/Green/Red/Gray). |
|
|
| `StatusIcon` | `string` | Icon text: `"..."`, `"OK"`, `"X"`, `"-"`. |
|
|
| `Steps` | `ObservableCollection<StepItem>` | Top-level steps in this journey. |
|
|
| `Breadcrumbs` | `List<BreadcrumbItem>` | Breadcrumbs captured during journey. |
|
|
| `Exceptions` | `List<ExceptionItem>` | Exceptions captured during journey. |
|
|
| `Metadata` | `Dictionary<string, object>` | Custom metadata. |
|
|
| `StepCount` | `int` | Total steps (including nested). |
|
|
| `FailedStepCount` | `int` | Number of failed steps. |
|
|
| `HasFailedSteps` | `bool` | Whether any steps failed. |
|
|
| `HasExceptions` | `bool` | Whether any exceptions occurred. |
|
|
| `HasUser` | `bool` | Whether a user is associated. |
|
|
|
|
#### StepItem Properties
|
|
|
|
| Property | Type | Description |
|
|
|----------|------|-------------|
|
|
| `StepId` | `string` | Unique step identifier. |
|
|
| `ParentStepId` | `string?` | Parent step ID for nested steps. |
|
|
| `Name` | `string` | Step name (e.g., "Process Payment"). |
|
|
| `Category` | `string?` | Category: `"business"`, `"technical"`, `"navigation"`. |
|
|
| `Status` | `StepDisplayStatus` | `InProgress`, `Completed`, `Failed`, `Skipped`. |
|
|
| `FailureReason` | `string?` | Reason for failure. |
|
|
| `StartTime` | `DateTime` | When the step started. |
|
|
| `EndTime` | `DateTime?` | When the step ended. |
|
|
| `DurationMs` | `double?` | Duration in milliseconds. |
|
|
| `DurationDisplay` | `string` | Formatted duration. |
|
|
| `StatusColor` | `Color` | Color for status indicator. |
|
|
| `StatusIcon` | `string` | Icon text. |
|
|
| `ChildSteps` | `ObservableCollection<StepItem>` | Nested child steps. |
|
|
| `NestingLevel` | `int` | Depth level for indentation. |
|
|
| `IndentMargin` | `Thickness` | Calculated margin for indentation. |
|
|
|
|
#### BreadcrumbItem Properties
|
|
|
|
| Property | Type | Description |
|
|
|----------|------|-------------|
|
|
| `Timestamp` | `DateTime` | When captured. |
|
|
| `Category` | `string` | Breadcrumb category. |
|
|
| `Message` | `string` | Breadcrumb message. |
|
|
| `Level` | `string` | Log level: `"info"`, `"warning"`, `"error"`, `"debug"`. |
|
|
| `LevelColor` | `Color` | Color based on level. |
|
|
| `Data` | `Dictionary<string, object>?` | Additional data. |
|
|
|
|
#### ExceptionItem Properties
|
|
|
|
| Property | Type | Description |
|
|
|----------|------|-------------|
|
|
| `Timestamp` | `DateTime` | When captured. |
|
|
| `ExceptionType` | `string` | Exception type name. |
|
|
| `Message` | `string` | Exception message. |
|
|
| `StackTrace` | `string?` | Stack trace. |
|
|
| `StepId` | `string?` | Step ID where exception occurred. |
|
|
| `HasStackTrace` | `bool` | Whether stack trace is available. |
|
|
|
|
#### Example
|
|
|
|
```csharp
|
|
// Basic usage
|
|
await Navigation.PushAsync(new UserJourneyView
|
|
{
|
|
TelemetryClient = _telemetryClient,
|
|
EnableLiveUpdates = true
|
|
});
|
|
|
|
// With view mode and filtering
|
|
var journeyView = new UserJourneyView
|
|
{
|
|
TelemetryClient = _telemetryClient,
|
|
ViewMode = JourneyViewMode.Tree,
|
|
UserIdFilter = currentUser.Id
|
|
};
|
|
await Navigation.PushAsync(journeyView);
|
|
|
|
// Event handling
|
|
var journeyView = new UserJourneyView { TelemetryClient = _telemetryClient };
|
|
journeyView.JourneySelected += (s, journey) =>
|
|
{
|
|
Debug.WriteLine($"Selected: {journey.Name} ({journey.StepCount} steps)");
|
|
};
|
|
journeyView.StepSelected += (s, step) =>
|
|
{
|
|
Debug.WriteLine($"Step: {step.Name} - {step.StatusDisplay}");
|
|
};
|
|
await Navigation.PushAsync(journeyView);
|
|
```
|
|
|
|
#### Detail Panel
|
|
|
|
When a journey is selected, a detail panel shows:
|
|
- **Status, Duration, User** - Summary info
|
|
- **Steps** - List of steps with status indicators and timing
|
|
- **Breadcrumbs** - Timestamped breadcrumb trail
|
|
- **Exceptions** - Exception details (tap to copy)
|
|
- **Metadata** - Key-value pairs
|
|
- **Copy to Clipboard** - Export journey details
|
|
|
|
#### Toolbar
|
|
|
|
- **Timeline/Tree/Flow** buttons - Switch view modes
|
|
- **Share** (Primary) - Export journeys as text
|
|
- **Clear** (Secondary) - Clear displayed journeys
|
|
- **Refresh** - Reload from TelemetryClient
|
|
|
|
---
|
|
|
|
## Services
|
|
|
|
### MauiSecureTokenStorage
|
|
|
|
Secure token storage using MAUI `SecureStorage`. Automatically registered when using `AddIronServices()`.
|
|
|
|
```csharp
|
|
// Automatically used by IronServicesClient
|
|
builder.Services.AddIronServices();
|
|
|
|
// Or manually
|
|
services.AddSingleton<ITokenStorage, MauiSecureTokenStorage>();
|
|
```
|
|
|
|
### ServiceCollectionExtensions
|
|
|
|
```csharp
|
|
// Default URLs
|
|
services.AddIronServices();
|
|
|
|
// Custom URLs
|
|
services.AddIronServices(options =>
|
|
{
|
|
options.LicensingUrl = "https://licensing.myapp.com";
|
|
options.NotifyUrl = "https://notify.myapp.com";
|
|
options.TelemetryUrl = "https://telemetry.myapp.com";
|
|
});
|
|
```
|
|
|
|
Registers:
|
|
- `MauiSecureTokenStorage` as `ITokenStorage`
|
|
- `IronServicesClient` as singleton
|
|
|
|
---
|
|
|
|
## Complete Example
|
|
|
|
```csharp
|
|
// MauiProgram.cs
|
|
public static MauiApp CreateMauiApp()
|
|
{
|
|
var builder = MauiApp.CreateBuilder();
|
|
builder.UseMauiApp<App>();
|
|
|
|
// Register IronServices
|
|
builder.Services.AddIronServices();
|
|
|
|
// Register TelemetryClient for logging
|
|
builder.Services.AddSingleton(sp => new TelemetryClient(new TelemetryOptions
|
|
{
|
|
Dsn = "your-dsn-here",
|
|
EnableOfflineQueue = true
|
|
}));
|
|
|
|
return builder.Build();
|
|
}
|
|
|
|
// LoginPage.xaml
|
|
<?xml version="1.0" encoding="utf-8" ?>
|
|
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
|
xmlns:iron="clr-namespace:IronServices.Maui.Controls;assembly=IronServices.Maui">
|
|
<ScrollView>
|
|
<iron:LoginView x:Name="LoginControl"
|
|
Client="{Binding IronClient}"
|
|
Logo="app_logo.png"
|
|
Title="Welcome"
|
|
LoginSuccess="OnLoginSuccess" />
|
|
</ScrollView>
|
|
</ContentPage>
|
|
|
|
// LoginPage.xaml.cs
|
|
public partial class LoginPage : ContentPage
|
|
{
|
|
public LoginPage(IronServicesClient client)
|
|
{
|
|
InitializeComponent();
|
|
LoginControl.Client = client;
|
|
}
|
|
|
|
private async void OnLoginSuccess(object sender, LoginSuccessEventArgs e)
|
|
{
|
|
await Shell.Current.GoToAsync("//main");
|
|
}
|
|
}
|
|
|
|
// SettingsPage.xaml.cs - Navigate to logs
|
|
private async void OnViewLogsClicked(object sender, EventArgs e)
|
|
{
|
|
var telemetry = Handler.MauiContext.Services.GetService<TelemetryClient>();
|
|
await Navigation.PushAsync(new AppLogView
|
|
{
|
|
TelemetryClient = telemetry,
|
|
EnableLiveUpdates = true
|
|
});
|
|
}
|
|
|
|
// SettingsPage.xaml.cs - Navigate to journeys
|
|
private async void OnViewJourneysClicked(object sender, EventArgs e)
|
|
{
|
|
var telemetry = Handler.MauiContext.Services.GetService<TelemetryClient>();
|
|
await Navigation.PushAsync(new UserJourneyView
|
|
{
|
|
TelemetryClient = telemetry,
|
|
EnableLiveUpdates = true,
|
|
ViewMode = JourneyViewMode.Timeline
|
|
});
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Styling
|
|
|
|
All controls use MAUI resource dictionaries. Override these colors in your `App.xaml`:
|
|
|
|
```xaml
|
|
<Color x:Key="Primary">#512BD4</Color>
|
|
<Color x:Key="Secondary">#DFD8F7</Color>
|
|
<Color x:Key="Gray100">#E1E1E1</Color>
|
|
<Color x:Key="Gray200">#C8C8C8</Color>
|
|
<Color x:Key="Gray400">#919191</Color>
|
|
<Color x:Key="Gray500">#6E6E6E</Color>
|
|
<Color x:Key="Gray600">#404040</Color>
|
|
<Color x:Key="Gray700">#374151</Color>
|
|
<Color x:Key="Gray800">#1F2937</Color>
|
|
<Color x:Key="Gray900">#212121</Color>
|
|
<Color x:Key="White">#FFFFFF</Color>
|
|
```
|
|
|
|
Controls support light/dark themes via `AppThemeBinding`.
|
|
|
|
---
|
|
|
|
## Platform Support
|
|
|
|
| Platform | Minimum Version |
|
|
|----------|-----------------|
|
|
| Android | API 21 (5.0) |
|
|
| iOS | 15.0 |
|
|
| macOS | 15.0 |
|
|
| Windows | 10.0.17763.0 |
|
|
|
|
---
|
|
|
|
## Dependencies
|
|
|
|
- `IronServices.Client` - Core API client
|
|
- `IronLicensing.Client` - License validation
|
|
- `IronTelemetry.Client` - Telemetry, journeys, and logging
|
|
- `Microsoft.Maui.Controls` - MAUI framework
|
|
|
|
---
|
|
|
|
## License
|
|
|
|
MIT License - see [LICENSE](LICENSE) file.
|