@openmdm/core 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +29 -4
- package/dist/index.js +118 -33
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +54 -1
- package/dist/types.js.map +1 -1
- package/package.json +3 -2
- package/src/index.ts +73 -28
- package/src/logger.ts +98 -0
- package/src/plugin-storage.ts +25 -5
- package/src/types.ts +61 -0
- package/src/webhooks.ts +15 -4
package/dist/types.d.ts
CHANGED
|
@@ -466,7 +466,58 @@ interface MDMConfig {
|
|
|
466
466
|
pluginStorage?: {
|
|
467
467
|
adapter: 'database' | 'memory';
|
|
468
468
|
};
|
|
469
|
+
/**
|
|
470
|
+
* Structured logger. Replaces OpenMDM's internal `console.*` calls
|
|
471
|
+
* so log output lands in the host application's logging pipeline
|
|
472
|
+
* (pino, winston, bunyan, OTEL collector, etc.) instead of raw
|
|
473
|
+
* stderr.
|
|
474
|
+
*
|
|
475
|
+
* The shape is a strict subset of the pino / winston / bunyan
|
|
476
|
+
* interface — any of those can be passed directly. If omitted, a
|
|
477
|
+
* default logger that writes to the console with an `[openmdm]`
|
|
478
|
+
* prefix is used. To silence OpenMDM entirely, pass a no-op
|
|
479
|
+
* implementation (see `createSilentLogger()` in the package
|
|
480
|
+
* exports).
|
|
481
|
+
*/
|
|
482
|
+
logger?: Logger;
|
|
469
483
|
}
|
|
484
|
+
/**
|
|
485
|
+
* Minimal structured-logger interface OpenMDM calls internally.
|
|
486
|
+
*
|
|
487
|
+
* The shape is deliberately the pino-compatible subset: an optional
|
|
488
|
+
* context object as the first argument followed by a message string.
|
|
489
|
+
* pino, winston, bunyan, and most other structured loggers accept
|
|
490
|
+
* this shape natively.
|
|
491
|
+
*
|
|
492
|
+
* Implementations should be side-effect-free on unconfigured levels
|
|
493
|
+
* (a production logger filtered to `info` should still accept
|
|
494
|
+
* `.debug()` calls cheaply).
|
|
495
|
+
*/
|
|
496
|
+
interface Logger {
|
|
497
|
+
/** Human-ignorable, high-volume tracing. Off in production by default. */
|
|
498
|
+
debug(context: LogContext, message: string): void;
|
|
499
|
+
debug(message: string): void;
|
|
500
|
+
/** Normal operational events. Enrollment, policy changes, command delivery. */
|
|
501
|
+
info(context: LogContext, message: string): void;
|
|
502
|
+
info(message: string): void;
|
|
503
|
+
/** Something is wrong but the server is still running. Retries, fallbacks, degraded modes. */
|
|
504
|
+
warn(context: LogContext, message: string): void;
|
|
505
|
+
warn(message: string): void;
|
|
506
|
+
/** Something failed and a request/operation did not complete. */
|
|
507
|
+
error(context: LogContext, message: string): void;
|
|
508
|
+
error(message: string): void;
|
|
509
|
+
/**
|
|
510
|
+
* Return a new logger with the given fields attached to every
|
|
511
|
+
* subsequent call. Used by managers and plugins to scope logs to
|
|
512
|
+
* a specific subsystem without repeating context.
|
|
513
|
+
*/
|
|
514
|
+
child(bindings: LogContext): Logger;
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Arbitrary structured context attached to a log line. Values must
|
|
518
|
+
* be JSON-serializable so host loggers can ship them to any backend.
|
|
519
|
+
*/
|
|
520
|
+
type LogContext = Record<string, unknown>;
|
|
470
521
|
interface StorageConfig {
|
|
471
522
|
/** Storage provider (s3, local, custom) */
|
|
472
523
|
provider: 's3' | 'local' | 'custom';
|
|
@@ -791,6 +842,8 @@ interface MDMInstance {
|
|
|
791
842
|
webhooks?: WebhookManager;
|
|
792
843
|
/** Database adapter */
|
|
793
844
|
db: DatabaseAdapter;
|
|
845
|
+
/** Structured logger. Already scoped to the `openmdm` namespace. Plugins should call `.child({...})` to scope further. */
|
|
846
|
+
logger: Logger;
|
|
794
847
|
/** Configuration */
|
|
795
848
|
config: MDMConfig;
|
|
796
849
|
/** Subscribe to events */
|
|
@@ -1491,4 +1544,4 @@ declare class ValidationError extends MDMError {
|
|
|
1491
1544
|
constructor(message: string, details?: unknown);
|
|
1492
1545
|
}
|
|
1493
1546
|
|
|
1494
|
-
export { type AppInstallationSummary, type AppRollback, type AppVersion, type Application, type ApplicationManager, ApplicationNotFoundError, type AuditAction, type AuditConfig, type AuditLog, type AuditLogFilter, type AuditLogListResult, type AuditManager, type AuditSummary, type AuthConfig, AuthenticationError, AuthorizationError, type AuthorizationManager, type Command, type CommandFilter, type CommandManager, CommandNotFoundError, type CommandResult, type CommandStatus, type CommandSuccessRates, type CommandType, type CreateAppRollbackInput, type CreateApplicationInput, type CreateAuditLogInput, type CreateDeviceInput, type CreateGroupInput, type CreatePolicyInput, type CreateRoleInput, type CreateScheduledTaskInput, type CreateTenantInput, type CreateUserInput, type DashboardManager, type DashboardStats, type DatabaseAdapter, type DeployTarget, type Device, type DeviceFilter, type DeviceListResult, type DeviceLocation, type DeviceManager, DeviceNotFoundError, type DeviceStatus, type DeviceStatusBreakdown, type EnqueueMessageInput, type EnrollmentConfig, EnrollmentError, type EnrollmentMethod, type EnrollmentRequest, type EnrollmentResponse, type EnrollmentTrendPoint, type EventFilter, type EventHandler, type EventPayloadMap, type EventType, type Group, type GroupHierarchyStats, type GroupManager, GroupNotFoundError, type GroupTreeNode, type HardwareControl, type Heartbeat, type InstalledApp, type MDMConfig, MDMError, type MDMEvent, type MDMInstance, type MDMPlugin, type MaintenanceWindow, type MessageQueueManager, type PasswordPolicy, type Permission, type PermissionAction, type PermissionResource, type PluginMiddleware, type PluginRoute, type PluginStorageAdapter, type PluginStorageEntry, type Policy, type PolicyApplication, type PolicyManager, PolicyNotFoundError, type PolicySettings, type PushAdapter, type PushBatchResult, type PushConfig, type PushMessage, type PushProviderConfig, type PushResult, type PushToken, type QueueMessageStatus, type QueueStats, type QueuedMessage, type RegisterPushTokenInput, type Role, RoleNotFoundError, type ScheduleManager, type ScheduledTask, type ScheduledTaskFilter, type ScheduledTaskListResult, type ScheduledTaskStatus, type SendCommandInput, type StorageConfig, type SystemUpdatePolicy, type TaskExecution, type TaskSchedule, type TaskType, type Tenant, type TenantFilter, type TenantListResult, type TenantManager, TenantNotFoundError, type TenantSettings, type TenantStats, type TenantStatus, type TimeWindow, type UpdateApplicationInput, type UpdateDeviceInput, type UpdateGroupInput, type UpdatePolicyInput, type UpdateRoleInput, type UpdateScheduledTaskInput, type UpdateTenantInput, type UpdateUserInput, type User, type UserFilter, type UserListResult, UserNotFoundError, type UserWithRoles, ValidationError, type VpnConfig, type WebhookConfig, type WebhookDeliveryResult, type WebhookEndpoint, type WebhookManager, type WifiConfig };
|
|
1547
|
+
export { type AppInstallationSummary, type AppRollback, type AppVersion, type Application, type ApplicationManager, ApplicationNotFoundError, type AuditAction, type AuditConfig, type AuditLog, type AuditLogFilter, type AuditLogListResult, type AuditManager, type AuditSummary, type AuthConfig, AuthenticationError, AuthorizationError, type AuthorizationManager, type Command, type CommandFilter, type CommandManager, CommandNotFoundError, type CommandResult, type CommandStatus, type CommandSuccessRates, type CommandType, type CreateAppRollbackInput, type CreateApplicationInput, type CreateAuditLogInput, type CreateDeviceInput, type CreateGroupInput, type CreatePolicyInput, type CreateRoleInput, type CreateScheduledTaskInput, type CreateTenantInput, type CreateUserInput, type DashboardManager, type DashboardStats, type DatabaseAdapter, type DeployTarget, type Device, type DeviceFilter, type DeviceListResult, type DeviceLocation, type DeviceManager, DeviceNotFoundError, type DeviceStatus, type DeviceStatusBreakdown, type EnqueueMessageInput, type EnrollmentConfig, EnrollmentError, type EnrollmentMethod, type EnrollmentRequest, type EnrollmentResponse, type EnrollmentTrendPoint, type EventFilter, type EventHandler, type EventPayloadMap, type EventType, type Group, type GroupHierarchyStats, type GroupManager, GroupNotFoundError, type GroupTreeNode, type HardwareControl, type Heartbeat, type InstalledApp, type LogContext, type Logger, type MDMConfig, MDMError, type MDMEvent, type MDMInstance, type MDMPlugin, type MaintenanceWindow, type MessageQueueManager, type PasswordPolicy, type Permission, type PermissionAction, type PermissionResource, type PluginMiddleware, type PluginRoute, type PluginStorageAdapter, type PluginStorageEntry, type Policy, type PolicyApplication, type PolicyManager, PolicyNotFoundError, type PolicySettings, type PushAdapter, type PushBatchResult, type PushConfig, type PushMessage, type PushProviderConfig, type PushResult, type PushToken, type QueueMessageStatus, type QueueStats, type QueuedMessage, type RegisterPushTokenInput, type Role, RoleNotFoundError, type ScheduleManager, type ScheduledTask, type ScheduledTaskFilter, type ScheduledTaskListResult, type ScheduledTaskStatus, type SendCommandInput, type StorageConfig, type SystemUpdatePolicy, type TaskExecution, type TaskSchedule, type TaskType, type Tenant, type TenantFilter, type TenantListResult, type TenantManager, TenantNotFoundError, type TenantSettings, type TenantStats, type TenantStatus, type TimeWindow, type UpdateApplicationInput, type UpdateDeviceInput, type UpdateGroupInput, type UpdatePolicyInput, type UpdateRoleInput, type UpdateScheduledTaskInput, type UpdateTenantInput, type UpdateUserInput, type User, type UserFilter, type UserListResult, UserNotFoundError, type UserWithRoles, ValidationError, type VpnConfig, type WebhookConfig, type WebhookDeliveryResult, type WebhookEndpoint, type WebhookManager, type WifiConfig };
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts"],"names":[],"mappings":";AAmwDO,IAAM,QAAA,GAAN,cAAuB,KAAA,CAAM;AAAA,EAClC,WAAA,CACE,OAAA,EACO,IAAA,EACA,UAAA,GAAqB,KACrB,OAAA,EACP;AACA,IAAA,KAAA,CAAM,OAAO,CAAA;AAJN,IAAA,IAAA,CAAA,IAAA,GAAA,IAAA;AACA,IAAA,IAAA,CAAA,UAAA,GAAA,UAAA;AACA,IAAA,IAAA,CAAA,OAAA,GAAA,OAAA;AAGP,IAAA,IAAA,CAAK,IAAA,GAAO,UAAA;AAAA,EACd;AACF;AAEO,IAAM,mBAAA,GAAN,cAAkC,QAAA,CAAS;AAAA,EAChD,YAAY,QAAA,EAAkB;AAC5B,IAAA,KAAA,CAAM,CAAA,kBAAA,EAAqB,QAAQ,CAAA,CAAA,EAAI,kBAAA,EAAoB,GAAG,CAAA;AAAA,EAChE;AACF;AAEO,IAAM,mBAAA,GAAN,cAAkC,QAAA,CAAS;AAAA,EAChD,YAAY,QAAA,EAAkB;AAC5B,IAAA,KAAA,CAAM,CAAA,kBAAA,EAAqB,QAAQ,CAAA,CAAA,EAAI,kBAAA,EAAoB,GAAG,CAAA;AAAA,EAChE;AACF;AAEO,IAAM,wBAAA,GAAN,cAAuC,QAAA,CAAS;AAAA,EACrD,YAAY,UAAA,EAAoB;AAC9B,IAAA,KAAA,CAAM,CAAA,uBAAA,EAA0B,UAAU,CAAA,CAAA,EAAI,uBAAA,EAAyB,GAAG,CAAA;AAAA,EAC5E;AACF;AAEO,IAAM,oBAAA,GAAN,cAAmC,QAAA,CAAS;AAAA,EACjD,YAAY,SAAA,EAAmB;AAC7B,IAAA,KAAA,CAAM,CAAA,mBAAA,EAAsB,SAAS,CAAA,CAAA,EAAI,mBAAA,EAAqB,GAAG,CAAA;AAAA,EACnE;AACF;AAEO,IAAM,mBAAA,GAAN,cAAkC,QAAA,CAAS;AAAA,EAChD,YAAY,UAAA,EAAoB;AAC9B,IAAA,KAAA,CAAM,CAAA,kBAAA,EAAqB,UAAU,CAAA,CAAA,EAAI,kBAAA,EAAoB,GAAG,CAAA;AAAA,EAClE;AACF;AAEO,IAAM,iBAAA,GAAN,cAAgC,QAAA,CAAS;AAAA,EAC9C,YAAY,UAAA,EAAoB;AAC9B,IAAA,KAAA,CAAM,CAAA,gBAAA,EAAmB,UAAU,CAAA,CAAA,EAAI,gBAAA,EAAkB,GAAG,CAAA;AAAA,EAC9D;AACF;AAEO,IAAM,kBAAA,GAAN,cAAiC,QAAA,CAAS;AAAA,EAC/C,YAAY,UAAA,EAAoB;AAC9B,IAAA,KAAA,CAAM,CAAA,iBAAA,EAAoB,UAAU,CAAA,CAAA,EAAI,iBAAA,EAAmB,GAAG,CAAA;AAAA,EAChE;AACF;AAEO,IAAM,iBAAA,GAAN,cAAgC,QAAA,CAAS;AAAA,EAC9C,YAAY,UAAA,EAAoB;AAC9B,IAAA,KAAA,CAAM,CAAA,gBAAA,EAAmB,UAAU,CAAA,CAAA,EAAI,gBAAA,EAAkB,GAAG,CAAA;AAAA,EAC9D;AACF;AAEO,IAAM,eAAA,GAAN,cAA8B,QAAA,CAAS;AAAA,EAC5C,WAAA,CAAY,SAAiB,OAAA,EAAmB;AAC9C,IAAA,KAAA,CAAM,OAAA,EAAS,kBAAA,EAAoB,GAAA,EAAK,OAAO,CAAA;AAAA,EACjD;AACF;AAEO,IAAM,mBAAA,GAAN,cAAkC,QAAA,CAAS;AAAA,EAChD,WAAA,CAAY,UAAkB,yBAAA,EAA2B;AACvD,IAAA,KAAA,CAAM,OAAA,EAAS,wBAAwB,GAAG,CAAA;AAAA,EAC5C;AACF;AAEO,IAAM,kBAAA,GAAN,cAAiC,QAAA,CAAS;AAAA,EAC/C,WAAA,CAAY,UAAkB,eAAA,EAAiB;AAC7C,IAAA,KAAA,CAAM,OAAA,EAAS,uBAAuB,GAAG,CAAA;AAAA,EAC3C;AACF;AAEO,IAAM,eAAA,GAAN,cAA8B,QAAA,CAAS;AAAA,EAC5C,WAAA,CAAY,SAAiB,OAAA,EAAmB;AAC9C,IAAA,KAAA,CAAM,OAAA,EAAS,kBAAA,EAAoB,GAAA,EAAK,OAAO,CAAA;AAAA,EACjD;AACF","file":"types.js","sourcesContent":["/**\n * OpenMDM Core Types\n *\n * These types define the core data structures for the MDM system.\n * Designed to be database-agnostic and framework-agnostic.\n */\n\n// ============================================\n// Device Types\n// ============================================\n\nexport type DeviceStatus = 'pending' | 'enrolled' | 'unenrolled' | 'blocked';\n\nexport interface Device {\n id: string;\n externalId?: string | null;\n enrollmentId: string;\n status: DeviceStatus;\n\n // Device Info\n model?: string | null;\n manufacturer?: string | null;\n osVersion?: string | null;\n serialNumber?: string | null;\n imei?: string | null;\n macAddress?: string | null;\n androidId?: string | null;\n\n // MDM State\n policyId?: string | null;\n agentVersion?: string | null; // MDM agent version installed on device\n lastHeartbeat?: Date | null;\n lastSync?: Date | null;\n\n // Telemetry\n batteryLevel?: number | null;\n storageUsed?: number | null;\n storageTotal?: number | null;\n location?: DeviceLocation | null;\n installedApps?: InstalledApp[] | null;\n\n // Metadata\n tags?: Record<string, string> | null;\n metadata?: Record<string, unknown> | null;\n\n // Timestamps\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface DeviceLocation {\n latitude: number;\n longitude: number;\n accuracy?: number;\n timestamp: Date;\n}\n\nexport interface InstalledApp {\n packageName: string;\n version: string;\n versionCode?: number;\n installedAt?: Date;\n}\n\nexport interface CreateDeviceInput {\n enrollmentId: string;\n externalId?: string;\n model?: string;\n manufacturer?: string;\n osVersion?: string;\n serialNumber?: string;\n imei?: string;\n macAddress?: string;\n androidId?: string;\n policyId?: string;\n tags?: Record<string, string>;\n metadata?: Record<string, unknown>;\n}\n\nexport interface UpdateDeviceInput {\n externalId?: string | null;\n status?: DeviceStatus;\n policyId?: string | null;\n agentVersion?: string | null;\n model?: string;\n manufacturer?: string;\n osVersion?: string;\n batteryLevel?: number | null;\n storageUsed?: number | null;\n storageTotal?: number | null;\n lastHeartbeat?: Date;\n lastSync?: Date;\n installedApps?: InstalledApp[];\n location?: DeviceLocation;\n tags?: Record<string, string>;\n metadata?: Record<string, unknown>;\n}\n\nexport interface DeviceFilter {\n status?: DeviceStatus | DeviceStatus[];\n policyId?: string;\n groupId?: string;\n search?: string;\n tags?: Record<string, string>;\n limit?: number;\n offset?: number;\n}\n\nexport interface DeviceListResult {\n devices: Device[];\n total: number;\n limit: number;\n offset: number;\n}\n\n// ============================================\n// Policy Types\n// ============================================\n\nexport interface Policy {\n id: string;\n name: string;\n description?: string | null;\n isDefault: boolean;\n settings: PolicySettings;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface PolicySettings {\n // Kiosk Mode\n kioskMode?: boolean;\n mainApp?: string;\n allowedApps?: string[];\n kioskExitPassword?: string;\n\n // Lock Features\n lockStatusBar?: boolean;\n lockNavigationBar?: boolean;\n lockSettings?: boolean;\n lockPowerButton?: boolean;\n blockInstall?: boolean;\n blockUninstall?: boolean;\n\n // Hardware Controls\n bluetooth?: HardwareControl;\n wifi?: HardwareControl;\n gps?: HardwareControl;\n mobileData?: HardwareControl;\n camera?: HardwareControl;\n microphone?: HardwareControl;\n usb?: HardwareControl;\n nfc?: HardwareControl;\n\n // Update Settings\n systemUpdatePolicy?: SystemUpdatePolicy;\n updateWindow?: TimeWindow;\n\n // Security\n passwordPolicy?: PasswordPolicy;\n encryptionRequired?: boolean;\n factoryResetProtection?: boolean;\n safeBootDisabled?: boolean;\n\n // Telemetry\n heartbeatInterval?: number;\n locationReportInterval?: number;\n locationEnabled?: boolean;\n\n // Network\n wifiConfigs?: WifiConfig[];\n vpnConfig?: VpnConfig;\n\n // Applications\n applications?: PolicyApplication[];\n\n // Custom settings (for plugins)\n custom?: Record<string, unknown>;\n}\n\nexport type HardwareControl = 'on' | 'off' | 'user';\nexport type SystemUpdatePolicy = 'auto' | 'windowed' | 'postpone' | 'manual';\n\nexport interface TimeWindow {\n start: string; // \"HH:MM\"\n end: string; // \"HH:MM\"\n}\n\nexport interface PasswordPolicy {\n required: boolean;\n minLength?: number;\n complexity?: 'none' | 'numeric' | 'alphanumeric' | 'complex';\n maxFailedAttempts?: number;\n expirationDays?: number;\n historyLength?: number;\n}\n\nexport interface WifiConfig {\n ssid: string;\n securityType: 'none' | 'wep' | 'wpa' | 'wpa2' | 'wpa3';\n password?: string;\n hidden?: boolean;\n autoConnect?: boolean;\n}\n\nexport interface VpnConfig {\n type: 'pptp' | 'l2tp' | 'ipsec' | 'openvpn' | 'wireguard';\n server: string;\n username?: string;\n password?: string;\n certificate?: string;\n config?: Record<string, unknown>;\n}\n\nexport interface PolicyApplication {\n packageName: string;\n action: 'install' | 'update' | 'uninstall';\n version?: string;\n required?: boolean;\n autoUpdate?: boolean;\n}\n\nexport interface CreatePolicyInput {\n name: string;\n description?: string;\n isDefault?: boolean;\n settings: PolicySettings;\n}\n\nexport interface UpdatePolicyInput {\n name?: string;\n description?: string | null;\n isDefault?: boolean;\n settings?: PolicySettings;\n}\n\n// ============================================\n// Application Types\n// ============================================\n\nexport interface Application {\n id: string;\n name: string;\n packageName: string;\n version: string;\n versionCode: number;\n url: string;\n hash?: string | null;\n size?: number | null;\n minSdkVersion?: number | null;\n\n // Deployment settings\n showIcon: boolean;\n runAfterInstall: boolean;\n runAtBoot: boolean;\n isSystem: boolean;\n\n // State\n isActive: boolean;\n\n // Metadata\n metadata?: Record<string, unknown> | null;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface CreateApplicationInput {\n name: string;\n packageName: string;\n version: string;\n versionCode: number;\n url: string;\n hash?: string;\n size?: number;\n minSdkVersion?: number;\n showIcon?: boolean;\n runAfterInstall?: boolean;\n runAtBoot?: boolean;\n isSystem?: boolean;\n metadata?: Record<string, unknown>;\n}\n\nexport interface UpdateApplicationInput {\n name?: string;\n version?: string;\n versionCode?: number;\n url?: string;\n hash?: string | null;\n size?: number | null;\n minSdkVersion?: number | null;\n showIcon?: boolean;\n runAfterInstall?: boolean;\n runAtBoot?: boolean;\n isActive?: boolean;\n metadata?: Record<string, unknown> | null;\n}\n\nexport interface DeployTarget {\n devices?: string[];\n policies?: string[];\n groups?: string[];\n}\n\n// ============================================\n// App Version & Rollback Types\n// ============================================\n\nexport interface AppVersion {\n id: string;\n applicationId: string;\n packageName: string;\n version: string;\n versionCode: number;\n url: string;\n hash?: string | null;\n size?: number | null;\n releaseNotes?: string | null;\n isMinimumVersion: boolean;\n createdAt: Date;\n}\n\nexport interface AppRollback {\n id: string;\n deviceId: string;\n packageName: string;\n fromVersion: string;\n fromVersionCode: number;\n toVersion: string;\n toVersionCode: number;\n reason?: string | null;\n status: 'pending' | 'in_progress' | 'completed' | 'failed';\n error?: string | null;\n initiatedBy?: string | null;\n createdAt: Date;\n completedAt?: Date | null;\n}\n\nexport interface CreateAppRollbackInput {\n deviceId: string;\n packageName: string;\n toVersionCode: number;\n reason?: string;\n initiatedBy?: string;\n}\n\n// ============================================\n// Command Types\n// ============================================\n\nexport type CommandType =\n | 'reboot'\n | 'shutdown'\n | 'sync'\n | 'lock'\n | 'unlock'\n | 'wipe'\n | 'factoryReset'\n | 'installApp'\n | 'uninstallApp'\n | 'updateApp'\n | 'runApp'\n | 'clearAppData'\n | 'clearAppCache'\n | 'shell'\n | 'setPolicy'\n | 'grantPermissions'\n | 'exitKiosk'\n | 'enterKiosk'\n | 'setWifi'\n | 'screenshot'\n | 'getLocation'\n | 'setVolume'\n | 'sendNotification'\n | 'whitelistBattery' // Whitelist app from battery optimization (Doze)\n | 'enablePermissiveMode' // Enable permissive mode for debugging\n | 'setTimeZone' // Set device timezone\n | 'enableAdb' // Enable/disable ADB debugging\n | 'rollbackApp' // Rollback to previous app version\n | 'updateAgent' // Update MDM agent to a new version\n | 'custom';\n\nexport type CommandStatus =\n | 'pending'\n | 'sent'\n | 'acknowledged'\n | 'completed'\n | 'failed'\n | 'cancelled';\n\nexport interface Command {\n id: string;\n deviceId: string;\n type: CommandType;\n payload?: Record<string, unknown> | null;\n status: CommandStatus;\n result?: CommandResult | null;\n error?: string | null;\n createdAt: Date;\n sentAt?: Date | null;\n acknowledgedAt?: Date | null;\n completedAt?: Date | null;\n}\n\nexport interface CommandResult {\n success: boolean;\n message?: string;\n data?: unknown;\n}\n\nexport interface SendCommandInput {\n deviceId: string;\n type: CommandType;\n payload?: Record<string, unknown>;\n}\n\nexport interface CommandFilter {\n deviceId?: string;\n status?: CommandStatus | CommandStatus[];\n type?: CommandType | CommandType[];\n limit?: number;\n offset?: number;\n}\n\n// ============================================\n// Event Types\n// ============================================\n\nexport type EventType =\n | 'device.enrolled'\n | 'device.unenrolled'\n | 'device.blocked'\n | 'device.heartbeat'\n | 'device.locationUpdated'\n | 'device.statusChanged'\n | 'device.policyChanged'\n | 'app.installed'\n | 'app.uninstalled'\n | 'app.updated'\n | 'app.crashed'\n | 'app.started'\n | 'app.stopped'\n | 'policy.applied'\n | 'policy.failed'\n | 'command.received'\n | 'command.acknowledged'\n | 'command.completed'\n | 'command.failed'\n | 'security.tamper'\n | 'security.rootDetected'\n | 'security.screenLocked'\n | 'security.screenUnlocked'\n | 'custom';\n\nexport interface MDMEvent<T = unknown> {\n id: string;\n deviceId: string;\n type: EventType;\n payload: T;\n createdAt: Date;\n}\n\nexport interface EventFilter {\n deviceId?: string;\n type?: EventType | EventType[];\n startDate?: Date;\n endDate?: Date;\n limit?: number;\n offset?: number;\n}\n\n// ============================================\n// Group Types\n// ============================================\n\nexport interface Group {\n id: string;\n name: string;\n description?: string | null;\n policyId?: string | null;\n parentId?: string | null;\n metadata?: Record<string, unknown> | null;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface CreateGroupInput {\n name: string;\n description?: string;\n policyId?: string;\n parentId?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface UpdateGroupInput {\n name?: string;\n description?: string | null;\n policyId?: string | null;\n parentId?: string | null;\n metadata?: Record<string, unknown> | null;\n}\n\n// ============================================\n// Enrollment Types\n// ============================================\n\nexport type EnrollmentMethod =\n | 'qr'\n | 'nfc'\n | 'zero-touch'\n | 'knox'\n | 'manual'\n | 'app-only'\n | 'adb';\n\nexport interface EnrollmentRequest {\n // Device identifiers (at least one required)\n macAddress?: string;\n serialNumber?: string;\n imei?: string;\n androidId?: string;\n\n // Device info\n model: string;\n manufacturer: string;\n osVersion: string;\n sdkVersion?: number;\n\n // Agent info\n agentVersion?: string;\n agentPackage?: string;\n\n // Enrollment details\n method: EnrollmentMethod;\n timestamp: string;\n signature: string;\n\n // Optional pre-assigned policy/group\n policyId?: string;\n groupId?: string;\n}\n\nexport interface EnrollmentResponse {\n deviceId: string;\n enrollmentId: string;\n policyId?: string;\n policy?: Policy;\n serverUrl: string;\n pushConfig: PushConfig;\n token: string;\n refreshToken?: string;\n tokenExpiresAt?: Date;\n}\n\nexport interface PushConfig {\n provider: 'fcm' | 'mqtt' | 'websocket' | 'polling';\n fcmSenderId?: string;\n mqttUrl?: string;\n mqttTopic?: string;\n mqttUsername?: string;\n mqttPassword?: string;\n wsUrl?: string;\n pollingInterval?: number;\n}\n\n// ============================================\n// Telemetry Types\n// ============================================\n\nexport interface Heartbeat {\n deviceId: string;\n timestamp: Date;\n\n // Battery\n batteryLevel: number;\n isCharging: boolean;\n batteryHealth?: 'good' | 'overheat' | 'dead' | 'cold' | 'unknown';\n\n // Storage\n storageUsed: number;\n storageTotal: number;\n\n // Memory\n memoryUsed: number;\n memoryTotal: number;\n\n // Network\n networkType?: 'wifi' | 'cellular' | 'ethernet' | 'none';\n networkName?: string; // SSID or carrier\n signalStrength?: number;\n ipAddress?: string;\n\n // Location\n location?: DeviceLocation;\n\n // Apps\n installedApps: InstalledApp[];\n runningApps?: string[];\n\n // Security\n isRooted?: boolean;\n isEncrypted?: boolean;\n screenLockEnabled?: boolean;\n\n // Agent status\n agentVersion?: string;\n policyVersion?: string;\n lastPolicySync?: Date;\n}\n\n// ============================================\n// Push Token Types\n// ============================================\n\nexport interface PushToken {\n id: string;\n deviceId: string;\n provider: 'fcm' | 'mqtt' | 'websocket';\n token: string;\n isActive: boolean;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface RegisterPushTokenInput {\n deviceId: string;\n provider: 'fcm' | 'mqtt' | 'websocket';\n token: string;\n}\n\n// ============================================\n// Configuration Types\n// ============================================\n\nexport interface MDMConfig {\n /** Database adapter for persistence */\n database: DatabaseAdapter;\n\n /** Authentication/authorization configuration */\n auth?: AuthConfig;\n\n /** Push notification provider configuration */\n push?: PushProviderConfig;\n\n /** Device enrollment configuration */\n enrollment?: EnrollmentConfig;\n\n /** Server URL (used in enrollment responses) */\n serverUrl?: string;\n\n /** APK/file storage configuration */\n storage?: StorageConfig;\n\n /** Outbound webhook configuration */\n webhooks?: WebhookConfig;\n\n /** Plugins to extend functionality */\n plugins?: MDMPlugin[];\n\n /** Event handlers */\n onDeviceEnrolled?: (device: Device) => Promise<void>;\n onDeviceUnenrolled?: (device: Device) => Promise<void>;\n onDeviceBlocked?: (device: Device) => Promise<void>;\n onHeartbeat?: (device: Device, heartbeat: Heartbeat) => Promise<void>;\n onCommand?: (command: Command) => Promise<void>;\n onEvent?: (event: MDMEvent) => Promise<void>;\n\n // Enterprise features\n /** Multi-tenancy configuration */\n multiTenancy?: {\n enabled: boolean;\n defaultTenantId?: string;\n tenantResolver?: (context: unknown) => Promise<string | null>;\n };\n\n /** Authorization (RBAC) configuration */\n authorization?: {\n enabled: boolean;\n defaultRole?: string;\n };\n\n /** Audit logging configuration */\n audit?: {\n enabled: boolean;\n retentionDays?: number;\n };\n\n /** Scheduling configuration */\n scheduling?: {\n enabled: boolean;\n timezone?: string;\n };\n\n /** Plugin storage configuration */\n pluginStorage?: {\n adapter: 'database' | 'memory';\n };\n}\n\nexport interface StorageConfig {\n /** Storage provider (s3, local, custom) */\n provider: 's3' | 'local' | 'custom';\n\n /** S3 configuration */\n s3?: {\n bucket: string;\n region: string;\n accessKeyId?: string;\n secretAccessKey?: string;\n endpoint?: string; // For S3-compatible services\n presignedUrlExpiry?: number; // Seconds, default 3600\n };\n\n /** Local storage path */\n localPath?: string;\n\n /** Custom storage adapter */\n customAdapter?: {\n upload: (file: Buffer, key: string) => Promise<string>;\n getUrl: (key: string) => Promise<string>;\n delete: (key: string) => Promise<void>;\n };\n}\n\nexport interface WebhookConfig {\n /** Webhook endpoints to notify */\n endpoints?: WebhookEndpoint[];\n\n /** Retry configuration */\n retry?: {\n maxRetries?: number;\n initialDelay?: number;\n maxDelay?: number;\n };\n\n /** Sign webhooks with HMAC secret */\n signingSecret?: string;\n}\n\nexport interface WebhookEndpoint {\n /** Unique identifier */\n id: string;\n /** Webhook URL */\n url: string;\n /** Events to trigger this webhook */\n events: (EventType | '*')[];\n /** Custom headers */\n headers?: Record<string, string>;\n /** Whether endpoint is active */\n enabled: boolean;\n}\n\nexport interface AuthConfig {\n /** Get current user from request context */\n getUser: <T = unknown>(context: unknown) => Promise<T | null>;\n /** Check if user has admin privileges */\n isAdmin?: (user: unknown) => Promise<boolean>;\n /** Check if user can access specific device */\n canAccessDevice?: (user: unknown, deviceId: string) => Promise<boolean>;\n /** Device JWT secret (for device auth tokens) */\n deviceTokenSecret?: string;\n /** Device token expiration in seconds (default: 365 days) */\n deviceTokenExpiration?: number;\n}\n\nexport interface PushProviderConfig {\n provider: 'fcm' | 'mqtt' | 'websocket' | 'polling';\n\n // FCM configuration\n fcmCredentials?: string | Record<string, unknown>;\n fcmProjectId?: string;\n\n // MQTT configuration\n mqttUrl?: string;\n mqttUsername?: string;\n mqttPassword?: string;\n mqttTopicPrefix?: string;\n\n // WebSocket configuration\n wsPath?: string;\n wsPingInterval?: number;\n\n // Polling fallback\n pollingInterval?: number;\n}\n\nexport interface EnrollmentConfig {\n /** Auto-enroll devices with valid signature */\n autoEnroll?: boolean;\n /** HMAC secret for device signature verification */\n deviceSecret: string;\n /** Allowed enrollment methods */\n allowedMethods?: EnrollmentMethod[];\n /** Default policy for new devices */\n defaultPolicyId?: string;\n /** Default group for new devices */\n defaultGroupId?: string;\n /** Require manual approval for enrollment */\n requireApproval?: boolean;\n /** Custom enrollment validation */\n validate?: (request: EnrollmentRequest) => Promise<boolean>;\n}\n\n// ============================================\n// Adapter Interfaces\n// ============================================\n\nexport interface DatabaseAdapter {\n // Devices\n findDevice(id: string): Promise<Device | null>;\n findDeviceByEnrollmentId(enrollmentId: string): Promise<Device | null>;\n listDevices(filter?: DeviceFilter): Promise<DeviceListResult>;\n createDevice(data: CreateDeviceInput): Promise<Device>;\n updateDevice(id: string, data: UpdateDeviceInput): Promise<Device>;\n deleteDevice(id: string): Promise<void>;\n countDevices(filter?: DeviceFilter): Promise<number>;\n\n // Policies\n findPolicy(id: string): Promise<Policy | null>;\n findDefaultPolicy(): Promise<Policy | null>;\n listPolicies(): Promise<Policy[]>;\n createPolicy(data: CreatePolicyInput): Promise<Policy>;\n updatePolicy(id: string, data: UpdatePolicyInput): Promise<Policy>;\n deletePolicy(id: string): Promise<void>;\n\n // Applications\n findApplication(id: string): Promise<Application | null>;\n findApplicationByPackage(packageName: string, version?: string): Promise<Application | null>;\n listApplications(activeOnly?: boolean): Promise<Application[]>;\n createApplication(data: CreateApplicationInput): Promise<Application>;\n updateApplication(id: string, data: UpdateApplicationInput): Promise<Application>;\n deleteApplication(id: string): Promise<void>;\n\n // Commands\n findCommand(id: string): Promise<Command | null>;\n listCommands(filter?: CommandFilter): Promise<Command[]>;\n createCommand(data: SendCommandInput): Promise<Command>;\n updateCommand(id: string, data: Partial<Command>): Promise<Command | null>;\n getPendingCommands(deviceId: string): Promise<Command[]>;\n\n // Events\n createEvent(event: Omit<MDMEvent, 'id' | 'createdAt'>): Promise<MDMEvent>;\n listEvents(filter?: EventFilter): Promise<MDMEvent[]>;\n\n // Groups\n findGroup(id: string): Promise<Group | null>;\n listGroups(): Promise<Group[]>;\n createGroup(data: CreateGroupInput): Promise<Group>;\n updateGroup(id: string, data: UpdateGroupInput): Promise<Group>;\n deleteGroup(id: string): Promise<void>;\n listDevicesInGroup(groupId: string): Promise<Device[]>;\n addDeviceToGroup(deviceId: string, groupId: string): Promise<void>;\n removeDeviceFromGroup(deviceId: string, groupId: string): Promise<void>;\n getDeviceGroups(deviceId: string): Promise<Group[]>;\n\n // Push Tokens\n findPushToken(deviceId: string, provider: string): Promise<PushToken | null>;\n upsertPushToken(data: RegisterPushTokenInput): Promise<PushToken>;\n deletePushToken(deviceId: string, provider?: string): Promise<void>;\n\n // App Versions (optional - for version tracking)\n listAppVersions?(packageName: string): Promise<AppVersion[]>;\n createAppVersion?(data: Omit<AppVersion, 'id' | 'createdAt'>): Promise<AppVersion>;\n setMinimumVersion?(packageName: string, versionCode: number): Promise<void>;\n getMinimumVersion?(packageName: string): Promise<AppVersion | null>;\n\n // Rollback History (optional)\n createRollback?(data: CreateAppRollbackInput): Promise<AppRollback>;\n updateRollback?(id: string, data: Partial<AppRollback>): Promise<AppRollback>;\n listRollbacks?(filter?: { deviceId?: string; packageName?: string }): Promise<AppRollback[]>;\n\n // Group Hierarchy (optional)\n getGroupChildren?(parentId: string | null): Promise<Group[]>;\n getGroupAncestors?(groupId: string): Promise<Group[]>;\n getGroupDescendants?(groupId: string): Promise<Group[]>;\n getGroupTree?(rootId?: string): Promise<GroupTreeNode[]>;\n getGroupEffectivePolicy?(groupId: string): Promise<Policy | null>;\n moveGroup?(groupId: string, newParentId: string | null): Promise<Group>;\n getGroupHierarchyStats?(): Promise<GroupHierarchyStats>;\n\n // Tenants (optional - for multi-tenancy)\n findTenant?(id: string): Promise<Tenant | null>;\n findTenantBySlug?(slug: string): Promise<Tenant | null>;\n listTenants?(filter?: TenantFilter): Promise<TenantListResult>;\n createTenant?(data: CreateTenantInput): Promise<Tenant>;\n updateTenant?(id: string, data: UpdateTenantInput): Promise<Tenant>;\n deleteTenant?(id: string): Promise<void>;\n getTenantStats?(tenantId: string): Promise<TenantStats>;\n\n // Users (optional - for RBAC)\n findUser?(id: string): Promise<User | null>;\n findUserByEmail?(email: string, tenantId?: string): Promise<User | null>;\n listUsers?(filter?: UserFilter): Promise<UserListResult>;\n createUser?(data: CreateUserInput): Promise<User>;\n updateUser?(id: string, data: UpdateUserInput): Promise<User>;\n deleteUser?(id: string): Promise<void>;\n\n // Roles (optional - for RBAC)\n findRole?(id: string): Promise<Role | null>;\n listRoles?(tenantId?: string): Promise<Role[]>;\n createRole?(data: CreateRoleInput): Promise<Role>;\n updateRole?(id: string, data: UpdateRoleInput): Promise<Role>;\n deleteRole?(id: string): Promise<void>;\n assignRoleToUser?(userId: string, roleId: string): Promise<void>;\n removeRoleFromUser?(userId: string, roleId: string): Promise<void>;\n getUserRoles?(userId: string): Promise<Role[]>;\n\n // Audit Logs (optional - for compliance)\n createAuditLog?(data: CreateAuditLogInput): Promise<AuditLog>;\n listAuditLogs?(filter?: AuditLogFilter): Promise<AuditLogListResult>;\n deleteAuditLogs?(filter: { olderThan?: Date; tenantId?: string }): Promise<number>;\n\n // Scheduled Tasks (optional - for scheduling)\n findScheduledTask?(id: string): Promise<ScheduledTask | null>;\n listScheduledTasks?(filter?: ScheduledTaskFilter): Promise<ScheduledTaskListResult>;\n createScheduledTask?(data: CreateScheduledTaskInput): Promise<ScheduledTask>;\n updateScheduledTask?(id: string, data: UpdateScheduledTaskInput): Promise<ScheduledTask>;\n deleteScheduledTask?(id: string): Promise<void>;\n getUpcomingTasks?(hours: number): Promise<ScheduledTask[]>;\n createTaskExecution?(data: { taskId: string }): Promise<TaskExecution>;\n updateTaskExecution?(id: string, data: Partial<TaskExecution>): Promise<TaskExecution>;\n listTaskExecutions?(taskId: string, limit?: number): Promise<TaskExecution[]>;\n\n // Message Queue (optional - for persistent messaging)\n enqueueMessage?(data: EnqueueMessageInput): Promise<QueuedMessage>;\n dequeueMessages?(deviceId: string, limit?: number): Promise<QueuedMessage[]>;\n peekMessages?(deviceId: string, limit?: number): Promise<QueuedMessage[]>;\n acknowledgeMessage?(messageId: string): Promise<void>;\n failMessage?(messageId: string, error: string): Promise<void>;\n retryFailedMessages?(maxAttempts?: number): Promise<number>;\n purgeExpiredMessages?(): Promise<number>;\n getQueueStats?(tenantId?: string): Promise<QueueStats>;\n\n // Plugin Storage (optional)\n getPluginValue?(pluginName: string, key: string): Promise<unknown | null>;\n setPluginValue?(pluginName: string, key: string, value: unknown): Promise<void>;\n deletePluginValue?(pluginName: string, key: string): Promise<void>;\n listPluginKeys?(pluginName: string, prefix?: string): Promise<string[]>;\n clearPluginData?(pluginName: string): Promise<void>;\n\n // Dashboard (optional - for analytics)\n getDashboardStats?(tenantId?: string): Promise<DashboardStats>;\n getDeviceStatusBreakdown?(tenantId?: string): Promise<DeviceStatusBreakdown>;\n getEnrollmentTrend?(days: number, tenantId?: string): Promise<EnrollmentTrendPoint[]>;\n getCommandSuccessRates?(tenantId?: string): Promise<CommandSuccessRates>;\n getAppInstallationSummary?(tenantId?: string): Promise<AppInstallationSummary>;\n\n // Transactions (optional)\n transaction?<T>(fn: () => Promise<T>): Promise<T>;\n}\n\nexport interface PushAdapter {\n /** Send push message to a device */\n send(deviceId: string, message: PushMessage): Promise<PushResult>;\n /** Send push message to multiple devices */\n sendBatch(deviceIds: string[], message: PushMessage): Promise<PushBatchResult>;\n /** Register device push token */\n registerToken?(deviceId: string, token: string): Promise<void>;\n /** Unregister device push token */\n unregisterToken?(deviceId: string): Promise<void>;\n /** Subscribe device to topic */\n subscribe?(deviceId: string, topic: string): Promise<void>;\n /** Unsubscribe device from topic */\n unsubscribe?(deviceId: string, topic: string): Promise<void>;\n}\n\nexport interface PushMessage {\n type: string;\n payload?: Record<string, unknown>;\n priority?: 'high' | 'normal';\n ttl?: number;\n collapseKey?: string;\n}\n\nexport interface PushResult {\n success: boolean;\n messageId?: string;\n error?: string;\n}\n\nexport interface PushBatchResult {\n successCount: number;\n failureCount: number;\n results: Array<{ deviceId: string; result: PushResult }>;\n}\n\n// ============================================\n// Plugin Interface\n// ============================================\n\nexport interface MDMPlugin {\n /** Unique plugin name */\n name: string;\n /** Plugin version */\n version: string;\n\n /** Called when MDM is initialized */\n onInit?(mdm: MDMInstance): Promise<void>;\n\n /** Called when MDM is destroyed */\n onDestroy?(): Promise<void>;\n\n /** Additional routes to mount */\n routes?: PluginRoute[];\n\n /** Middleware to apply to all routes */\n middleware?: PluginMiddleware[];\n\n /** Extend enrollment process */\n onEnroll?(device: Device, request: EnrollmentRequest): Promise<void>;\n\n /** Extend device processing */\n onDeviceEnrolled?(device: Device): Promise<void>;\n onDeviceUnenrolled?(device: Device): Promise<void>;\n onHeartbeat?(device: Device, heartbeat: Heartbeat): Promise<void>;\n\n /** Extend policy processing */\n policySchema?: Record<string, unknown>;\n validatePolicy?(settings: PolicySettings): Promise<{ valid: boolean; errors?: string[] }>;\n applyPolicy?(device: Device, policy: Policy): Promise<void>;\n\n /** Extend command processing */\n commandTypes?: CommandType[];\n executeCommand?(device: Device, command: Command): Promise<CommandResult>;\n}\n\nexport interface PluginRoute {\n method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\n path: string;\n handler: (context: unknown) => Promise<unknown>;\n auth?: boolean;\n admin?: boolean;\n}\n\nexport type PluginMiddleware = (\n context: unknown,\n next: () => Promise<unknown>\n) => Promise<unknown>;\n\n// ============================================\n// MDM Instance Interface\n// ============================================\n\nexport interface WebhookManager {\n /** Deliver an event to all matching webhook endpoints */\n deliver<T>(event: MDMEvent<T>): Promise<WebhookDeliveryResult[]>;\n /** Add a webhook endpoint at runtime */\n addEndpoint(endpoint: WebhookEndpoint): void;\n /** Remove a webhook endpoint */\n removeEndpoint(endpointId: string): void;\n /** Update a webhook endpoint */\n updateEndpoint(endpointId: string, updates: Partial<WebhookEndpoint>): void;\n /** Get all configured endpoints */\n getEndpoints(): WebhookEndpoint[];\n /** Test a webhook endpoint with a test payload */\n testEndpoint(endpointId: string): Promise<WebhookDeliveryResult>;\n}\n\nexport interface WebhookDeliveryResult {\n endpointId: string;\n success: boolean;\n statusCode?: number;\n error?: string;\n retryCount: number;\n deliveredAt?: Date;\n}\n\nexport interface MDMInstance {\n /** Device management */\n devices: DeviceManager;\n /** Policy management */\n policies: PolicyManager;\n /** Application management */\n apps: ApplicationManager;\n /** Command management */\n commands: CommandManager;\n /** Group management */\n groups: GroupManager;\n\n /** Tenant management (if multi-tenancy enabled) */\n tenants?: TenantManager;\n /** Authorization management (RBAC) */\n authorization?: AuthorizationManager;\n /** Audit logging */\n audit?: AuditManager;\n /** Scheduled task management */\n schedules?: ScheduleManager;\n /** Persistent message queue */\n messageQueue?: MessageQueueManager;\n /** Dashboard analytics */\n dashboard?: DashboardManager;\n /** Plugin storage */\n pluginStorage?: PluginStorageAdapter;\n\n /** Push notification service */\n push: PushAdapter;\n\n /** Webhook delivery (if configured) */\n webhooks?: WebhookManager;\n\n /** Database adapter */\n db: DatabaseAdapter;\n\n /** Configuration */\n config: MDMConfig;\n\n /** Subscribe to events */\n on<T extends EventType>(event: T, handler: EventHandler<T>): () => void;\n /** Emit an event */\n emit<T extends EventType>(event: T, data: EventPayloadMap[T]): Promise<void>;\n\n /** Process device enrollment */\n enroll(request: EnrollmentRequest): Promise<EnrollmentResponse>;\n /** Process device heartbeat */\n processHeartbeat(deviceId: string, heartbeat: Heartbeat): Promise<void>;\n /** Verify device token */\n verifyDeviceToken(token: string): Promise<{ deviceId: string } | null>;\n\n /** Get loaded plugins */\n getPlugins(): MDMPlugin[];\n /** Get plugin by name */\n getPlugin(name: string): MDMPlugin | undefined;\n}\n\n// ============================================\n// Manager Interfaces\n// ============================================\n\nexport interface DeviceManager {\n get(id: string): Promise<Device | null>;\n getByEnrollmentId(enrollmentId: string): Promise<Device | null>;\n list(filter?: DeviceFilter): Promise<DeviceListResult>;\n create(data: CreateDeviceInput): Promise<Device>;\n update(id: string, data: UpdateDeviceInput): Promise<Device>;\n delete(id: string): Promise<void>;\n assignPolicy(deviceId: string, policyId: string | null): Promise<Device>;\n addToGroup(deviceId: string, groupId: string): Promise<void>;\n removeFromGroup(deviceId: string, groupId: string): Promise<void>;\n getGroups(deviceId: string): Promise<Group[]>;\n sendCommand(deviceId: string, input: Omit<SendCommandInput, 'deviceId'>): Promise<Command>;\n sync(deviceId: string): Promise<Command>;\n reboot(deviceId: string): Promise<Command>;\n lock(deviceId: string, message?: string): Promise<Command>;\n wipe(deviceId: string, preserveData?: boolean): Promise<Command>;\n}\n\nexport interface PolicyManager {\n get(id: string): Promise<Policy | null>;\n getDefault(): Promise<Policy | null>;\n list(): Promise<Policy[]>;\n create(data: CreatePolicyInput): Promise<Policy>;\n update(id: string, data: UpdatePolicyInput): Promise<Policy>;\n delete(id: string): Promise<void>;\n setDefault(id: string): Promise<Policy>;\n getDevices(policyId: string): Promise<Device[]>;\n applyToDevice(policyId: string, deviceId: string): Promise<void>;\n}\n\nexport interface ApplicationManager {\n get(id: string): Promise<Application | null>;\n getByPackage(packageName: string, version?: string): Promise<Application | null>;\n list(activeOnly?: boolean): Promise<Application[]>;\n register(data: CreateApplicationInput): Promise<Application>;\n update(id: string, data: UpdateApplicationInput): Promise<Application>;\n delete(id: string): Promise<void>;\n activate(id: string): Promise<Application>;\n deactivate(id: string): Promise<Application>;\n deploy(packageName: string, target: DeployTarget): Promise<void>;\n installOnDevice(packageName: string, deviceId: string, version?: string): Promise<Command>;\n uninstallFromDevice(packageName: string, deviceId: string): Promise<Command>;\n}\n\nexport interface CommandManager {\n get(id: string): Promise<Command | null>;\n list(filter?: CommandFilter): Promise<Command[]>;\n send(input: SendCommandInput): Promise<Command>;\n cancel(id: string): Promise<Command>;\n acknowledge(id: string): Promise<Command>;\n complete(id: string, result: CommandResult): Promise<Command>;\n fail(id: string, error: string): Promise<Command>;\n getPending(deviceId: string): Promise<Command[]>;\n}\n\nexport interface GroupManager {\n // Basic CRUD operations\n get(id: string): Promise<Group | null>;\n list(): Promise<Group[]>;\n create(data: CreateGroupInput): Promise<Group>;\n update(id: string, data: UpdateGroupInput): Promise<Group>;\n delete(id: string): Promise<void>;\n\n // Device management\n getDevices(groupId: string): Promise<Device[]>;\n addDevice(groupId: string, deviceId: string): Promise<void>;\n removeDevice(groupId: string, deviceId: string): Promise<void>;\n\n // Hierarchy operations\n getChildren(groupId: string): Promise<Group[]>;\n getTree(rootId?: string): Promise<GroupTreeNode[]>;\n getAncestors(groupId: string): Promise<Group[]>;\n getDescendants(groupId: string): Promise<Group[]>;\n move(groupId: string, newParentId: string | null): Promise<Group>;\n getEffectivePolicy(groupId: string): Promise<Policy | null>;\n getHierarchyStats(): Promise<GroupHierarchyStats>;\n}\n\n// ============================================\n// Group Hierarchy Types\n// ============================================\n\nexport interface GroupTreeNode extends Group {\n children: GroupTreeNode[];\n depth: number;\n path: string[];\n effectivePolicyId?: string | null;\n}\n\nexport interface GroupHierarchyStats {\n totalGroups: number;\n maxDepth: number;\n groupsWithDevices: number;\n groupsWithPolicies: number;\n}\n\n// ============================================\n// Tenant Types (Multi-tenancy)\n// ============================================\n\nexport type TenantStatus = 'active' | 'suspended' | 'pending';\n\nexport interface Tenant {\n id: string;\n name: string;\n slug: string;\n status: TenantStatus;\n settings?: TenantSettings | null;\n metadata?: Record<string, unknown> | null;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface TenantSettings {\n maxDevices?: number;\n maxUsers?: number;\n features?: string[];\n branding?: {\n logo?: string;\n primaryColor?: string;\n };\n}\n\nexport interface CreateTenantInput {\n name: string;\n slug: string;\n settings?: TenantSettings;\n metadata?: Record<string, unknown>;\n}\n\nexport interface UpdateTenantInput {\n name?: string;\n slug?: string;\n status?: TenantStatus;\n settings?: TenantSettings;\n metadata?: Record<string, unknown>;\n}\n\nexport interface TenantFilter {\n status?: TenantStatus;\n search?: string;\n limit?: number;\n offset?: number;\n}\n\nexport interface TenantListResult {\n tenants: Tenant[];\n total: number;\n limit: number;\n offset: number;\n}\n\nexport interface TenantStats {\n deviceCount: number;\n userCount: number;\n policyCount: number;\n appCount: number;\n}\n\n// ============================================\n// RBAC Types (Role-Based Access Control)\n// ============================================\n\nexport type PermissionAction = 'create' | 'read' | 'update' | 'delete' | 'manage' | '*';\nexport type PermissionResource = 'devices' | 'policies' | 'apps' | 'groups' | 'commands' | 'users' | 'roles' | 'tenants' | 'audit' | '*';\n\nexport interface Permission {\n action: PermissionAction;\n resource: PermissionResource;\n resourceId?: string;\n}\n\nexport interface Role {\n id: string;\n tenantId?: string | null;\n name: string;\n description?: string | null;\n permissions: Permission[];\n isSystem: boolean;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface CreateRoleInput {\n tenantId?: string;\n name: string;\n description?: string;\n permissions: Permission[];\n}\n\nexport interface UpdateRoleInput {\n name?: string;\n description?: string;\n permissions?: Permission[];\n}\n\nexport interface User {\n id: string;\n tenantId?: string | null;\n email: string;\n name?: string | null;\n status: 'active' | 'inactive' | 'pending';\n metadata?: Record<string, unknown> | null;\n lastLoginAt?: Date | null;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface UserWithRoles extends User {\n roles: Role[];\n}\n\nexport interface CreateUserInput {\n tenantId?: string;\n email: string;\n name?: string;\n status?: 'active' | 'inactive' | 'pending';\n metadata?: Record<string, unknown>;\n}\n\nexport interface UpdateUserInput {\n email?: string;\n name?: string;\n status?: 'active' | 'inactive' | 'pending';\n metadata?: Record<string, unknown>;\n}\n\nexport interface UserFilter {\n tenantId?: string;\n status?: 'active' | 'inactive' | 'pending';\n search?: string;\n limit?: number;\n offset?: number;\n}\n\nexport interface UserListResult {\n users: User[];\n total: number;\n limit: number;\n offset: number;\n}\n\n// ============================================\n// Audit Types\n// ============================================\n\nexport type AuditAction = 'create' | 'read' | 'update' | 'delete' | 'login' | 'logout' | 'enroll' | 'unenroll' | 'command' | 'export' | 'import' | 'custom';\n\nexport interface AuditLog {\n id: string;\n tenantId?: string | null;\n userId?: string | null;\n action: AuditAction;\n resource: string;\n resourceId?: string | null;\n status: 'success' | 'failure';\n error?: string | null;\n details?: Record<string, unknown> | null;\n ipAddress?: string | null;\n userAgent?: string | null;\n createdAt: Date;\n}\n\nexport interface CreateAuditLogInput {\n tenantId?: string;\n userId?: string;\n action: AuditAction;\n resource: string;\n resourceId?: string;\n status?: 'success' | 'failure';\n error?: string;\n details?: Record<string, unknown>;\n ipAddress?: string;\n userAgent?: string;\n}\n\nexport interface AuditConfig {\n enabled: boolean;\n retentionDays?: number;\n skipReadOperations?: boolean;\n logActions?: AuditAction[];\n logResources?: string[];\n}\n\nexport interface AuditSummary {\n totalLogs: number;\n byAction: Record<AuditAction, number>;\n byResource: Record<string, number>;\n byStatus: { success: number; failure: number };\n topUsers: Array<{ userId: string; count: number }>;\n recentFailures: AuditLog[];\n}\n\nexport interface AuditLogFilter {\n tenantId?: string;\n userId?: string;\n action?: string;\n resource?: string;\n resourceId?: string;\n startDate?: Date;\n endDate?: Date;\n limit?: number;\n offset?: number;\n}\n\nexport interface AuditLogListResult {\n logs: AuditLog[];\n total: number;\n limit: number;\n offset: number;\n}\n\n// ============================================\n// Schedule Types\n// ============================================\n\nexport type TaskType = 'command' | 'policy_update' | 'app_install' | 'maintenance' | 'custom';\nexport type ScheduledTaskStatus = 'active' | 'paused' | 'completed' | 'failed';\n\nexport interface MaintenanceWindow {\n daysOfWeek: number[];\n startTime: string;\n endTime: string;\n timezone: string;\n}\n\nexport interface TaskSchedule {\n type: 'once' | 'recurring' | 'window';\n executeAt?: Date;\n cron?: string;\n window?: MaintenanceWindow;\n}\n\nexport interface ScheduledTask {\n id: string;\n tenantId?: string | null;\n name: string;\n description?: string | null;\n taskType: TaskType;\n schedule: TaskSchedule;\n target?: DeployTarget;\n payload?: Record<string, unknown> | null;\n status: ScheduledTaskStatus;\n nextRunAt?: Date | null;\n lastRunAt?: Date | null;\n maxRetries: number;\n retryCount: number;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface CreateScheduledTaskInput {\n tenantId?: string;\n name: string;\n description?: string;\n taskType: TaskType;\n schedule: TaskSchedule;\n target?: DeployTarget;\n payload?: Record<string, unknown>;\n maxRetries?: number;\n}\n\nexport interface UpdateScheduledTaskInput {\n name?: string;\n description?: string;\n schedule?: TaskSchedule;\n target?: DeployTarget;\n payload?: Record<string, unknown>;\n status?: ScheduledTaskStatus;\n maxRetries?: number;\n}\n\nexport interface ScheduledTaskFilter {\n tenantId?: string;\n taskType?: TaskType | TaskType[];\n status?: ScheduledTaskStatus | ScheduledTaskStatus[];\n limit?: number;\n offset?: number;\n}\n\nexport interface ScheduledTaskListResult {\n tasks: ScheduledTask[];\n total: number;\n limit: number;\n offset: number;\n}\n\nexport interface TaskExecution {\n id: string;\n taskId: string;\n status: 'running' | 'completed' | 'failed';\n startedAt: Date;\n completedAt?: Date | null;\n devicesProcessed: number;\n devicesSucceeded: number;\n devicesFailed: number;\n error?: string | null;\n details?: Record<string, unknown> | null;\n}\n\n// ============================================\n// Message Queue Types\n// ============================================\n\nexport type QueueMessageStatus = 'pending' | 'processing' | 'delivered' | 'failed' | 'expired';\n\nexport interface QueuedMessage {\n id: string;\n tenantId?: string | null;\n deviceId: string;\n messageType: string;\n payload: Record<string, unknown>;\n priority: 'high' | 'normal' | 'low';\n status: QueueMessageStatus;\n attempts: number;\n maxAttempts: number;\n lastAttemptAt?: Date | null;\n lastError?: string | null;\n expiresAt?: Date | null;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface EnqueueMessageInput {\n tenantId?: string;\n deviceId: string;\n messageType: string;\n payload: Record<string, unknown>;\n priority?: 'high' | 'normal' | 'low';\n maxAttempts?: number;\n ttlSeconds?: number;\n}\n\nexport interface QueueStats {\n pending: number;\n processing: number;\n delivered: number;\n failed: number;\n expired: number;\n byDevice: Record<string, number>;\n oldestPending?: Date;\n}\n\n// ============================================\n// Dashboard Types\n// ============================================\n\nexport interface DashboardStats {\n devices: {\n total: number;\n enrolled: number;\n active: number;\n blocked: number;\n pending: number;\n };\n policies: {\n total: number;\n deployed: number;\n };\n applications: {\n total: number;\n deployed: number;\n };\n commands: {\n pendingCount: number;\n last24hTotal: number;\n last24hSuccess: number;\n last24hFailed: number;\n };\n groups: {\n total: number;\n withDevices: number;\n };\n}\n\nexport interface DeviceStatusBreakdown {\n byStatus: Record<DeviceStatus, number>;\n byOs: Record<string, number>;\n byManufacturer: Record<string, number>;\n byModel: Record<string, number>;\n}\n\nexport interface EnrollmentTrendPoint {\n date: Date;\n enrolled: number;\n unenrolled: number;\n netChange: number;\n totalDevices: number;\n}\n\nexport interface CommandSuccessRates {\n overall: {\n total: number;\n completed: number;\n failed: number;\n successRate: number;\n };\n byType: Record<string, {\n total: number;\n completed: number;\n failed: number;\n successRate: number;\n avgExecutionTimeMs?: number;\n }>;\n last24h: {\n total: number;\n completed: number;\n failed: number;\n pending: number;\n };\n}\n\nexport interface AppInstallationSummary {\n total: number;\n byStatus: Record<string, number>;\n recentFailures: Array<{\n packageName: string;\n deviceId: string;\n error: string;\n timestamp: Date;\n }>;\n topInstalled: Array<{\n packageName: string;\n name: string;\n installedCount: number;\n }>;\n}\n\n// ============================================\n// Plugin Storage Types\n// ============================================\n\nexport interface PluginStorageAdapter {\n get<T>(pluginName: string, key: string): Promise<T | null>;\n set<T>(pluginName: string, key: string, value: T): Promise<void>;\n delete(pluginName: string, key: string): Promise<void>;\n list(pluginName: string, prefix?: string): Promise<string[]>;\n clear(pluginName: string): Promise<void>;\n}\n\nexport interface PluginStorageEntry {\n pluginName: string;\n key: string;\n value: unknown;\n createdAt: Date;\n updatedAt: Date;\n}\n\n// ============================================\n// Enterprise Manager Interfaces\n// ============================================\n\nexport interface TenantManager {\n get(id: string): Promise<Tenant | null>;\n getBySlug(slug: string): Promise<Tenant | null>;\n list(filter?: TenantFilter): Promise<TenantListResult>;\n create(data: CreateTenantInput): Promise<Tenant>;\n update(id: string, data: UpdateTenantInput): Promise<Tenant>;\n delete(id: string, cascade?: boolean): Promise<void>;\n getStats(tenantId: string): Promise<TenantStats>;\n activate(id: string): Promise<Tenant>;\n deactivate(id: string): Promise<Tenant>;\n}\n\nexport interface AuthorizationManager {\n createRole(data: CreateRoleInput): Promise<Role>;\n getRole(id: string): Promise<Role | null>;\n listRoles(tenantId?: string): Promise<Role[]>;\n updateRole(id: string, data: UpdateRoleInput): Promise<Role>;\n deleteRole(id: string): Promise<void>;\n createUser(data: CreateUserInput): Promise<User>;\n getUser(id: string): Promise<UserWithRoles | null>;\n getUserByEmail(email: string, tenantId?: string): Promise<UserWithRoles | null>;\n listUsers(filter?: UserFilter): Promise<UserListResult>;\n updateUser(id: string, data: UpdateUserInput): Promise<User>;\n deleteUser(id: string): Promise<void>;\n assignRole(userId: string, roleId: string): Promise<void>;\n removeRole(userId: string, roleId: string): Promise<void>;\n getUserRoles(userId: string): Promise<Role[]>;\n can(userId: string, action: PermissionAction, resource: PermissionResource, resourceId?: string): Promise<boolean>;\n canAny(userId: string, permissions: Array<{ action: PermissionAction; resource: PermissionResource }>): Promise<boolean>;\n requirePermission(userId: string, action: PermissionAction, resource: PermissionResource, resourceId?: string): Promise<void>;\n isAdmin(userId: string): Promise<boolean>;\n}\n\nexport interface AuditManager {\n log(entry: CreateAuditLogInput): Promise<AuditLog>;\n list(filter?: AuditLogFilter): Promise<AuditLogListResult>;\n getByResource(resource: string, resourceId: string): Promise<AuditLog[]>;\n getByUser(userId: string, filter?: AuditLogFilter): Promise<AuditLogListResult>;\n export(filter: AuditLogFilter, format: 'json' | 'csv'): Promise<string>;\n purge(olderThanDays?: number): Promise<number>;\n getSummary(tenantId?: string, days?: number): Promise<AuditSummary>;\n}\n\nexport interface ScheduleManager {\n get(id: string): Promise<ScheduledTask | null>;\n list(filter?: ScheduledTaskFilter): Promise<ScheduledTaskListResult>;\n create(data: CreateScheduledTaskInput): Promise<ScheduledTask>;\n update(id: string, data: UpdateScheduledTaskInput): Promise<ScheduledTask>;\n delete(id: string): Promise<void>;\n pause(id: string): Promise<ScheduledTask>;\n resume(id: string): Promise<ScheduledTask>;\n runNow(id: string): Promise<TaskExecution>;\n getUpcoming(hours: number): Promise<ScheduledTask[]>;\n getExecutions(taskId: string, limit?: number): Promise<TaskExecution[]>;\n calculateNextRun(schedule: TaskSchedule): Date | null;\n}\n\nexport interface MessageQueueManager {\n enqueue(message: EnqueueMessageInput): Promise<QueuedMessage>;\n enqueueBatch(messages: EnqueueMessageInput[]): Promise<QueuedMessage[]>;\n dequeue(deviceId: string, limit?: number): Promise<QueuedMessage[]>;\n acknowledge(messageId: string): Promise<void>;\n fail(messageId: string, error: string): Promise<void>;\n retryFailed(maxAttempts?: number): Promise<number>;\n purgeExpired(): Promise<number>;\n getStats(tenantId?: string): Promise<QueueStats>;\n peek(deviceId: string, limit?: number): Promise<QueuedMessage[]>;\n}\n\nexport interface DashboardManager {\n getStats(tenantId?: string): Promise<DashboardStats>;\n getDeviceStatusBreakdown(tenantId?: string): Promise<DeviceStatusBreakdown>;\n getEnrollmentTrend(days: number, tenantId?: string): Promise<EnrollmentTrendPoint[]>;\n getCommandSuccessRates(tenantId?: string): Promise<CommandSuccessRates>;\n getAppInstallationSummary(tenantId?: string): Promise<AppInstallationSummary>;\n}\n\n// ============================================\n// Event Handler Types\n// ============================================\n\nexport type EventHandler<T extends EventType> = (\n event: MDMEvent<EventPayloadMap[T]>\n) => Promise<void> | void;\n\nexport interface EventPayloadMap {\n 'device.enrolled': { device: Device };\n 'device.unenrolled': { device: Device; reason?: string };\n 'device.blocked': { device: Device; reason: string };\n 'device.heartbeat': { device: Device; heartbeat: Heartbeat };\n 'device.locationUpdated': { device: Device; location: DeviceLocation };\n 'device.statusChanged': { device: Device; oldStatus: DeviceStatus; newStatus: DeviceStatus };\n 'device.policyChanged': { device: Device; oldPolicyId?: string; newPolicyId?: string };\n 'app.installed': { device: Device; app: InstalledApp };\n 'app.uninstalled': { device: Device; packageName: string };\n 'app.updated': { device: Device; app: InstalledApp; oldVersion: string };\n 'app.crashed': { device: Device; packageName: string; error?: string };\n 'app.started': { device: Device; packageName: string };\n 'app.stopped': { device: Device; packageName: string };\n 'policy.applied': { device: Device; policy: Policy };\n 'policy.failed': { device: Device; policy: Policy; error: string };\n 'command.received': { device: Device; command: Command };\n 'command.acknowledged': { device: Device; command: Command };\n 'command.completed': { device: Device; command: Command; result: CommandResult };\n 'command.failed': { device: Device; command: Command; error: string };\n 'security.tamper': { device: Device; type: string; details?: unknown };\n 'security.rootDetected': { device: Device };\n 'security.screenLocked': { device: Device };\n 'security.screenUnlocked': { device: Device };\n custom: Record<string, unknown>;\n}\n\n// ============================================\n// Error Types\n// ============================================\n\nexport class MDMError extends Error {\n constructor(\n message: string,\n public code: string,\n public statusCode: number = 500,\n public details?: unknown\n ) {\n super(message);\n this.name = 'MDMError';\n }\n}\n\nexport class DeviceNotFoundError extends MDMError {\n constructor(deviceId: string) {\n super(`Device not found: ${deviceId}`, 'DEVICE_NOT_FOUND', 404);\n }\n}\n\nexport class PolicyNotFoundError extends MDMError {\n constructor(policyId: string) {\n super(`Policy not found: ${policyId}`, 'POLICY_NOT_FOUND', 404);\n }\n}\n\nexport class ApplicationNotFoundError extends MDMError {\n constructor(identifier: string) {\n super(`Application not found: ${identifier}`, 'APPLICATION_NOT_FOUND', 404);\n }\n}\n\nexport class CommandNotFoundError extends MDMError {\n constructor(commandId: string) {\n super(`Command not found: ${commandId}`, 'COMMAND_NOT_FOUND', 404);\n }\n}\n\nexport class TenantNotFoundError extends MDMError {\n constructor(identifier: string) {\n super(`Tenant not found: ${identifier}`, 'TENANT_NOT_FOUND', 404);\n }\n}\n\nexport class RoleNotFoundError extends MDMError {\n constructor(identifier: string) {\n super(`Role not found: ${identifier}`, 'ROLE_NOT_FOUND', 404);\n }\n}\n\nexport class GroupNotFoundError extends MDMError {\n constructor(identifier: string) {\n super(`Group not found: ${identifier}`, 'GROUP_NOT_FOUND', 404);\n }\n}\n\nexport class UserNotFoundError extends MDMError {\n constructor(identifier: string) {\n super(`User not found: ${identifier}`, 'USER_NOT_FOUND', 404);\n }\n}\n\nexport class EnrollmentError extends MDMError {\n constructor(message: string, details?: unknown) {\n super(message, 'ENROLLMENT_ERROR', 400, details);\n }\n}\n\nexport class AuthenticationError extends MDMError {\n constructor(message: string = 'Authentication required') {\n super(message, 'AUTHENTICATION_ERROR', 401);\n }\n}\n\nexport class AuthorizationError extends MDMError {\n constructor(message: string = 'Access denied') {\n super(message, 'AUTHORIZATION_ERROR', 403);\n }\n}\n\nexport class ValidationError extends MDMError {\n constructor(message: string, details?: unknown) {\n super(message, 'VALIDATION_ERROR', 400, details);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/types.ts"],"names":[],"mappings":";AAg0DO,IAAM,QAAA,GAAN,cAAuB,KAAA,CAAM;AAAA,EAClC,WAAA,CACE,OAAA,EACO,IAAA,EACA,UAAA,GAAqB,KACrB,OAAA,EACP;AACA,IAAA,KAAA,CAAM,OAAO,CAAA;AAJN,IAAA,IAAA,CAAA,IAAA,GAAA,IAAA;AACA,IAAA,IAAA,CAAA,UAAA,GAAA,UAAA;AACA,IAAA,IAAA,CAAA,OAAA,GAAA,OAAA;AAGP,IAAA,IAAA,CAAK,IAAA,GAAO,UAAA;AAAA,EACd;AACF;AAEO,IAAM,mBAAA,GAAN,cAAkC,QAAA,CAAS;AAAA,EAChD,YAAY,QAAA,EAAkB;AAC5B,IAAA,KAAA,CAAM,CAAA,kBAAA,EAAqB,QAAQ,CAAA,CAAA,EAAI,kBAAA,EAAoB,GAAG,CAAA;AAAA,EAChE;AACF;AAEO,IAAM,mBAAA,GAAN,cAAkC,QAAA,CAAS;AAAA,EAChD,YAAY,QAAA,EAAkB;AAC5B,IAAA,KAAA,CAAM,CAAA,kBAAA,EAAqB,QAAQ,CAAA,CAAA,EAAI,kBAAA,EAAoB,GAAG,CAAA;AAAA,EAChE;AACF;AAEO,IAAM,wBAAA,GAAN,cAAuC,QAAA,CAAS;AAAA,EACrD,YAAY,UAAA,EAAoB;AAC9B,IAAA,KAAA,CAAM,CAAA,uBAAA,EAA0B,UAAU,CAAA,CAAA,EAAI,uBAAA,EAAyB,GAAG,CAAA;AAAA,EAC5E;AACF;AAEO,IAAM,oBAAA,GAAN,cAAmC,QAAA,CAAS;AAAA,EACjD,YAAY,SAAA,EAAmB;AAC7B,IAAA,KAAA,CAAM,CAAA,mBAAA,EAAsB,SAAS,CAAA,CAAA,EAAI,mBAAA,EAAqB,GAAG,CAAA;AAAA,EACnE;AACF;AAEO,IAAM,mBAAA,GAAN,cAAkC,QAAA,CAAS;AAAA,EAChD,YAAY,UAAA,EAAoB;AAC9B,IAAA,KAAA,CAAM,CAAA,kBAAA,EAAqB,UAAU,CAAA,CAAA,EAAI,kBAAA,EAAoB,GAAG,CAAA;AAAA,EAClE;AACF;AAEO,IAAM,iBAAA,GAAN,cAAgC,QAAA,CAAS;AAAA,EAC9C,YAAY,UAAA,EAAoB;AAC9B,IAAA,KAAA,CAAM,CAAA,gBAAA,EAAmB,UAAU,CAAA,CAAA,EAAI,gBAAA,EAAkB,GAAG,CAAA;AAAA,EAC9D;AACF;AAEO,IAAM,kBAAA,GAAN,cAAiC,QAAA,CAAS;AAAA,EAC/C,YAAY,UAAA,EAAoB;AAC9B,IAAA,KAAA,CAAM,CAAA,iBAAA,EAAoB,UAAU,CAAA,CAAA,EAAI,iBAAA,EAAmB,GAAG,CAAA;AAAA,EAChE;AACF;AAEO,IAAM,iBAAA,GAAN,cAAgC,QAAA,CAAS;AAAA,EAC9C,YAAY,UAAA,EAAoB;AAC9B,IAAA,KAAA,CAAM,CAAA,gBAAA,EAAmB,UAAU,CAAA,CAAA,EAAI,gBAAA,EAAkB,GAAG,CAAA;AAAA,EAC9D;AACF;AAEO,IAAM,eAAA,GAAN,cAA8B,QAAA,CAAS;AAAA,EAC5C,WAAA,CAAY,SAAiB,OAAA,EAAmB;AAC9C,IAAA,KAAA,CAAM,OAAA,EAAS,kBAAA,EAAoB,GAAA,EAAK,OAAO,CAAA;AAAA,EACjD;AACF;AAEO,IAAM,mBAAA,GAAN,cAAkC,QAAA,CAAS;AAAA,EAChD,WAAA,CAAY,UAAkB,yBAAA,EAA2B;AACvD,IAAA,KAAA,CAAM,OAAA,EAAS,wBAAwB,GAAG,CAAA;AAAA,EAC5C;AACF;AAEO,IAAM,kBAAA,GAAN,cAAiC,QAAA,CAAS;AAAA,EAC/C,WAAA,CAAY,UAAkB,eAAA,EAAiB;AAC7C,IAAA,KAAA,CAAM,OAAA,EAAS,uBAAuB,GAAG,CAAA;AAAA,EAC3C;AACF;AAEO,IAAM,eAAA,GAAN,cAA8B,QAAA,CAAS;AAAA,EAC5C,WAAA,CAAY,SAAiB,OAAA,EAAmB;AAC9C,IAAA,KAAA,CAAM,OAAA,EAAS,kBAAA,EAAoB,GAAA,EAAK,OAAO,CAAA;AAAA,EACjD;AACF","file":"types.js","sourcesContent":["/**\n * OpenMDM Core Types\n *\n * These types define the core data structures for the MDM system.\n * Designed to be database-agnostic and framework-agnostic.\n */\n\n// ============================================\n// Device Types\n// ============================================\n\nexport type DeviceStatus = 'pending' | 'enrolled' | 'unenrolled' | 'blocked';\n\nexport interface Device {\n id: string;\n externalId?: string | null;\n enrollmentId: string;\n status: DeviceStatus;\n\n // Device Info\n model?: string | null;\n manufacturer?: string | null;\n osVersion?: string | null;\n serialNumber?: string | null;\n imei?: string | null;\n macAddress?: string | null;\n androidId?: string | null;\n\n // MDM State\n policyId?: string | null;\n agentVersion?: string | null; // MDM agent version installed on device\n lastHeartbeat?: Date | null;\n lastSync?: Date | null;\n\n // Telemetry\n batteryLevel?: number | null;\n storageUsed?: number | null;\n storageTotal?: number | null;\n location?: DeviceLocation | null;\n installedApps?: InstalledApp[] | null;\n\n // Metadata\n tags?: Record<string, string> | null;\n metadata?: Record<string, unknown> | null;\n\n // Timestamps\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface DeviceLocation {\n latitude: number;\n longitude: number;\n accuracy?: number;\n timestamp: Date;\n}\n\nexport interface InstalledApp {\n packageName: string;\n version: string;\n versionCode?: number;\n installedAt?: Date;\n}\n\nexport interface CreateDeviceInput {\n enrollmentId: string;\n externalId?: string;\n model?: string;\n manufacturer?: string;\n osVersion?: string;\n serialNumber?: string;\n imei?: string;\n macAddress?: string;\n androidId?: string;\n policyId?: string;\n tags?: Record<string, string>;\n metadata?: Record<string, unknown>;\n}\n\nexport interface UpdateDeviceInput {\n externalId?: string | null;\n status?: DeviceStatus;\n policyId?: string | null;\n agentVersion?: string | null;\n model?: string;\n manufacturer?: string;\n osVersion?: string;\n batteryLevel?: number | null;\n storageUsed?: number | null;\n storageTotal?: number | null;\n lastHeartbeat?: Date;\n lastSync?: Date;\n installedApps?: InstalledApp[];\n location?: DeviceLocation;\n tags?: Record<string, string>;\n metadata?: Record<string, unknown>;\n}\n\nexport interface DeviceFilter {\n status?: DeviceStatus | DeviceStatus[];\n policyId?: string;\n groupId?: string;\n search?: string;\n tags?: Record<string, string>;\n limit?: number;\n offset?: number;\n}\n\nexport interface DeviceListResult {\n devices: Device[];\n total: number;\n limit: number;\n offset: number;\n}\n\n// ============================================\n// Policy Types\n// ============================================\n\nexport interface Policy {\n id: string;\n name: string;\n description?: string | null;\n isDefault: boolean;\n settings: PolicySettings;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface PolicySettings {\n // Kiosk Mode\n kioskMode?: boolean;\n mainApp?: string;\n allowedApps?: string[];\n kioskExitPassword?: string;\n\n // Lock Features\n lockStatusBar?: boolean;\n lockNavigationBar?: boolean;\n lockSettings?: boolean;\n lockPowerButton?: boolean;\n blockInstall?: boolean;\n blockUninstall?: boolean;\n\n // Hardware Controls\n bluetooth?: HardwareControl;\n wifi?: HardwareControl;\n gps?: HardwareControl;\n mobileData?: HardwareControl;\n camera?: HardwareControl;\n microphone?: HardwareControl;\n usb?: HardwareControl;\n nfc?: HardwareControl;\n\n // Update Settings\n systemUpdatePolicy?: SystemUpdatePolicy;\n updateWindow?: TimeWindow;\n\n // Security\n passwordPolicy?: PasswordPolicy;\n encryptionRequired?: boolean;\n factoryResetProtection?: boolean;\n safeBootDisabled?: boolean;\n\n // Telemetry\n heartbeatInterval?: number;\n locationReportInterval?: number;\n locationEnabled?: boolean;\n\n // Network\n wifiConfigs?: WifiConfig[];\n vpnConfig?: VpnConfig;\n\n // Applications\n applications?: PolicyApplication[];\n\n // Custom settings (for plugins)\n custom?: Record<string, unknown>;\n}\n\nexport type HardwareControl = 'on' | 'off' | 'user';\nexport type SystemUpdatePolicy = 'auto' | 'windowed' | 'postpone' | 'manual';\n\nexport interface TimeWindow {\n start: string; // \"HH:MM\"\n end: string; // \"HH:MM\"\n}\n\nexport interface PasswordPolicy {\n required: boolean;\n minLength?: number;\n complexity?: 'none' | 'numeric' | 'alphanumeric' | 'complex';\n maxFailedAttempts?: number;\n expirationDays?: number;\n historyLength?: number;\n}\n\nexport interface WifiConfig {\n ssid: string;\n securityType: 'none' | 'wep' | 'wpa' | 'wpa2' | 'wpa3';\n password?: string;\n hidden?: boolean;\n autoConnect?: boolean;\n}\n\nexport interface VpnConfig {\n type: 'pptp' | 'l2tp' | 'ipsec' | 'openvpn' | 'wireguard';\n server: string;\n username?: string;\n password?: string;\n certificate?: string;\n config?: Record<string, unknown>;\n}\n\nexport interface PolicyApplication {\n packageName: string;\n action: 'install' | 'update' | 'uninstall';\n version?: string;\n required?: boolean;\n autoUpdate?: boolean;\n}\n\nexport interface CreatePolicyInput {\n name: string;\n description?: string;\n isDefault?: boolean;\n settings: PolicySettings;\n}\n\nexport interface UpdatePolicyInput {\n name?: string;\n description?: string | null;\n isDefault?: boolean;\n settings?: PolicySettings;\n}\n\n// ============================================\n// Application Types\n// ============================================\n\nexport interface Application {\n id: string;\n name: string;\n packageName: string;\n version: string;\n versionCode: number;\n url: string;\n hash?: string | null;\n size?: number | null;\n minSdkVersion?: number | null;\n\n // Deployment settings\n showIcon: boolean;\n runAfterInstall: boolean;\n runAtBoot: boolean;\n isSystem: boolean;\n\n // State\n isActive: boolean;\n\n // Metadata\n metadata?: Record<string, unknown> | null;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface CreateApplicationInput {\n name: string;\n packageName: string;\n version: string;\n versionCode: number;\n url: string;\n hash?: string;\n size?: number;\n minSdkVersion?: number;\n showIcon?: boolean;\n runAfterInstall?: boolean;\n runAtBoot?: boolean;\n isSystem?: boolean;\n metadata?: Record<string, unknown>;\n}\n\nexport interface UpdateApplicationInput {\n name?: string;\n version?: string;\n versionCode?: number;\n url?: string;\n hash?: string | null;\n size?: number | null;\n minSdkVersion?: number | null;\n showIcon?: boolean;\n runAfterInstall?: boolean;\n runAtBoot?: boolean;\n isActive?: boolean;\n metadata?: Record<string, unknown> | null;\n}\n\nexport interface DeployTarget {\n devices?: string[];\n policies?: string[];\n groups?: string[];\n}\n\n// ============================================\n// App Version & Rollback Types\n// ============================================\n\nexport interface AppVersion {\n id: string;\n applicationId: string;\n packageName: string;\n version: string;\n versionCode: number;\n url: string;\n hash?: string | null;\n size?: number | null;\n releaseNotes?: string | null;\n isMinimumVersion: boolean;\n createdAt: Date;\n}\n\nexport interface AppRollback {\n id: string;\n deviceId: string;\n packageName: string;\n fromVersion: string;\n fromVersionCode: number;\n toVersion: string;\n toVersionCode: number;\n reason?: string | null;\n status: 'pending' | 'in_progress' | 'completed' | 'failed';\n error?: string | null;\n initiatedBy?: string | null;\n createdAt: Date;\n completedAt?: Date | null;\n}\n\nexport interface CreateAppRollbackInput {\n deviceId: string;\n packageName: string;\n toVersionCode: number;\n reason?: string;\n initiatedBy?: string;\n}\n\n// ============================================\n// Command Types\n// ============================================\n\nexport type CommandType =\n | 'reboot'\n | 'shutdown'\n | 'sync'\n | 'lock'\n | 'unlock'\n | 'wipe'\n | 'factoryReset'\n | 'installApp'\n | 'uninstallApp'\n | 'updateApp'\n | 'runApp'\n | 'clearAppData'\n | 'clearAppCache'\n | 'shell'\n | 'setPolicy'\n | 'grantPermissions'\n | 'exitKiosk'\n | 'enterKiosk'\n | 'setWifi'\n | 'screenshot'\n | 'getLocation'\n | 'setVolume'\n | 'sendNotification'\n | 'whitelistBattery' // Whitelist app from battery optimization (Doze)\n | 'enablePermissiveMode' // Enable permissive mode for debugging\n | 'setTimeZone' // Set device timezone\n | 'enableAdb' // Enable/disable ADB debugging\n | 'rollbackApp' // Rollback to previous app version\n | 'updateAgent' // Update MDM agent to a new version\n | 'custom';\n\nexport type CommandStatus =\n | 'pending'\n | 'sent'\n | 'acknowledged'\n | 'completed'\n | 'failed'\n | 'cancelled';\n\nexport interface Command {\n id: string;\n deviceId: string;\n type: CommandType;\n payload?: Record<string, unknown> | null;\n status: CommandStatus;\n result?: CommandResult | null;\n error?: string | null;\n createdAt: Date;\n sentAt?: Date | null;\n acknowledgedAt?: Date | null;\n completedAt?: Date | null;\n}\n\nexport interface CommandResult {\n success: boolean;\n message?: string;\n data?: unknown;\n}\n\nexport interface SendCommandInput {\n deviceId: string;\n type: CommandType;\n payload?: Record<string, unknown>;\n}\n\nexport interface CommandFilter {\n deviceId?: string;\n status?: CommandStatus | CommandStatus[];\n type?: CommandType | CommandType[];\n limit?: number;\n offset?: number;\n}\n\n// ============================================\n// Event Types\n// ============================================\n\nexport type EventType =\n | 'device.enrolled'\n | 'device.unenrolled'\n | 'device.blocked'\n | 'device.heartbeat'\n | 'device.locationUpdated'\n | 'device.statusChanged'\n | 'device.policyChanged'\n | 'app.installed'\n | 'app.uninstalled'\n | 'app.updated'\n | 'app.crashed'\n | 'app.started'\n | 'app.stopped'\n | 'policy.applied'\n | 'policy.failed'\n | 'command.received'\n | 'command.acknowledged'\n | 'command.completed'\n | 'command.failed'\n | 'security.tamper'\n | 'security.rootDetected'\n | 'security.screenLocked'\n | 'security.screenUnlocked'\n | 'custom';\n\nexport interface MDMEvent<T = unknown> {\n id: string;\n deviceId: string;\n type: EventType;\n payload: T;\n createdAt: Date;\n}\n\nexport interface EventFilter {\n deviceId?: string;\n type?: EventType | EventType[];\n startDate?: Date;\n endDate?: Date;\n limit?: number;\n offset?: number;\n}\n\n// ============================================\n// Group Types\n// ============================================\n\nexport interface Group {\n id: string;\n name: string;\n description?: string | null;\n policyId?: string | null;\n parentId?: string | null;\n metadata?: Record<string, unknown> | null;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface CreateGroupInput {\n name: string;\n description?: string;\n policyId?: string;\n parentId?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface UpdateGroupInput {\n name?: string;\n description?: string | null;\n policyId?: string | null;\n parentId?: string | null;\n metadata?: Record<string, unknown> | null;\n}\n\n// ============================================\n// Enrollment Types\n// ============================================\n\nexport type EnrollmentMethod =\n | 'qr'\n | 'nfc'\n | 'zero-touch'\n | 'knox'\n | 'manual'\n | 'app-only'\n | 'adb';\n\nexport interface EnrollmentRequest {\n // Device identifiers (at least one required)\n macAddress?: string;\n serialNumber?: string;\n imei?: string;\n androidId?: string;\n\n // Device info\n model: string;\n manufacturer: string;\n osVersion: string;\n sdkVersion?: number;\n\n // Agent info\n agentVersion?: string;\n agentPackage?: string;\n\n // Enrollment details\n method: EnrollmentMethod;\n timestamp: string;\n signature: string;\n\n // Optional pre-assigned policy/group\n policyId?: string;\n groupId?: string;\n}\n\nexport interface EnrollmentResponse {\n deviceId: string;\n enrollmentId: string;\n policyId?: string;\n policy?: Policy;\n serverUrl: string;\n pushConfig: PushConfig;\n token: string;\n refreshToken?: string;\n tokenExpiresAt?: Date;\n}\n\nexport interface PushConfig {\n provider: 'fcm' | 'mqtt' | 'websocket' | 'polling';\n fcmSenderId?: string;\n mqttUrl?: string;\n mqttTopic?: string;\n mqttUsername?: string;\n mqttPassword?: string;\n wsUrl?: string;\n pollingInterval?: number;\n}\n\n// ============================================\n// Telemetry Types\n// ============================================\n\nexport interface Heartbeat {\n deviceId: string;\n timestamp: Date;\n\n // Battery\n batteryLevel: number;\n isCharging: boolean;\n batteryHealth?: 'good' | 'overheat' | 'dead' | 'cold' | 'unknown';\n\n // Storage\n storageUsed: number;\n storageTotal: number;\n\n // Memory\n memoryUsed: number;\n memoryTotal: number;\n\n // Network\n networkType?: 'wifi' | 'cellular' | 'ethernet' | 'none';\n networkName?: string; // SSID or carrier\n signalStrength?: number;\n ipAddress?: string;\n\n // Location\n location?: DeviceLocation;\n\n // Apps\n installedApps: InstalledApp[];\n runningApps?: string[];\n\n // Security\n isRooted?: boolean;\n isEncrypted?: boolean;\n screenLockEnabled?: boolean;\n\n // Agent status\n agentVersion?: string;\n policyVersion?: string;\n lastPolicySync?: Date;\n}\n\n// ============================================\n// Push Token Types\n// ============================================\n\nexport interface PushToken {\n id: string;\n deviceId: string;\n provider: 'fcm' | 'mqtt' | 'websocket';\n token: string;\n isActive: boolean;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface RegisterPushTokenInput {\n deviceId: string;\n provider: 'fcm' | 'mqtt' | 'websocket';\n token: string;\n}\n\n// ============================================\n// Configuration Types\n// ============================================\n\nexport interface MDMConfig {\n /** Database adapter for persistence */\n database: DatabaseAdapter;\n\n /** Authentication/authorization configuration */\n auth?: AuthConfig;\n\n /** Push notification provider configuration */\n push?: PushProviderConfig;\n\n /** Device enrollment configuration */\n enrollment?: EnrollmentConfig;\n\n /** Server URL (used in enrollment responses) */\n serverUrl?: string;\n\n /** APK/file storage configuration */\n storage?: StorageConfig;\n\n /** Outbound webhook configuration */\n webhooks?: WebhookConfig;\n\n /** Plugins to extend functionality */\n plugins?: MDMPlugin[];\n\n /** Event handlers */\n onDeviceEnrolled?: (device: Device) => Promise<void>;\n onDeviceUnenrolled?: (device: Device) => Promise<void>;\n onDeviceBlocked?: (device: Device) => Promise<void>;\n onHeartbeat?: (device: Device, heartbeat: Heartbeat) => Promise<void>;\n onCommand?: (command: Command) => Promise<void>;\n onEvent?: (event: MDMEvent) => Promise<void>;\n\n // Enterprise features\n /** Multi-tenancy configuration */\n multiTenancy?: {\n enabled: boolean;\n defaultTenantId?: string;\n tenantResolver?: (context: unknown) => Promise<string | null>;\n };\n\n /** Authorization (RBAC) configuration */\n authorization?: {\n enabled: boolean;\n defaultRole?: string;\n };\n\n /** Audit logging configuration */\n audit?: {\n enabled: boolean;\n retentionDays?: number;\n };\n\n /** Scheduling configuration */\n scheduling?: {\n enabled: boolean;\n timezone?: string;\n };\n\n /** Plugin storage configuration */\n pluginStorage?: {\n adapter: 'database' | 'memory';\n };\n\n /**\n * Structured logger. Replaces OpenMDM's internal `console.*` calls\n * so log output lands in the host application's logging pipeline\n * (pino, winston, bunyan, OTEL collector, etc.) instead of raw\n * stderr.\n *\n * The shape is a strict subset of the pino / winston / bunyan\n * interface — any of those can be passed directly. If omitted, a\n * default logger that writes to the console with an `[openmdm]`\n * prefix is used. To silence OpenMDM entirely, pass a no-op\n * implementation (see `createSilentLogger()` in the package\n * exports).\n */\n logger?: Logger;\n}\n\n/**\n * Minimal structured-logger interface OpenMDM calls internally.\n *\n * The shape is deliberately the pino-compatible subset: an optional\n * context object as the first argument followed by a message string.\n * pino, winston, bunyan, and most other structured loggers accept\n * this shape natively.\n *\n * Implementations should be side-effect-free on unconfigured levels\n * (a production logger filtered to `info` should still accept\n * `.debug()` calls cheaply).\n */\nexport interface Logger {\n /** Human-ignorable, high-volume tracing. Off in production by default. */\n debug(context: LogContext, message: string): void;\n debug(message: string): void;\n\n /** Normal operational events. Enrollment, policy changes, command delivery. */\n info(context: LogContext, message: string): void;\n info(message: string): void;\n\n /** Something is wrong but the server is still running. Retries, fallbacks, degraded modes. */\n warn(context: LogContext, message: string): void;\n warn(message: string): void;\n\n /** Something failed and a request/operation did not complete. */\n error(context: LogContext, message: string): void;\n error(message: string): void;\n\n /**\n * Return a new logger with the given fields attached to every\n * subsequent call. Used by managers and plugins to scope logs to\n * a specific subsystem without repeating context.\n */\n child(bindings: LogContext): Logger;\n}\n\n/**\n * Arbitrary structured context attached to a log line. Values must\n * be JSON-serializable so host loggers can ship them to any backend.\n */\nexport type LogContext = Record<string, unknown>;\n\nexport interface StorageConfig {\n /** Storage provider (s3, local, custom) */\n provider: 's3' | 'local' | 'custom';\n\n /** S3 configuration */\n s3?: {\n bucket: string;\n region: string;\n accessKeyId?: string;\n secretAccessKey?: string;\n endpoint?: string; // For S3-compatible services\n presignedUrlExpiry?: number; // Seconds, default 3600\n };\n\n /** Local storage path */\n localPath?: string;\n\n /** Custom storage adapter */\n customAdapter?: {\n upload: (file: Buffer, key: string) => Promise<string>;\n getUrl: (key: string) => Promise<string>;\n delete: (key: string) => Promise<void>;\n };\n}\n\nexport interface WebhookConfig {\n /** Webhook endpoints to notify */\n endpoints?: WebhookEndpoint[];\n\n /** Retry configuration */\n retry?: {\n maxRetries?: number;\n initialDelay?: number;\n maxDelay?: number;\n };\n\n /** Sign webhooks with HMAC secret */\n signingSecret?: string;\n}\n\nexport interface WebhookEndpoint {\n /** Unique identifier */\n id: string;\n /** Webhook URL */\n url: string;\n /** Events to trigger this webhook */\n events: (EventType | '*')[];\n /** Custom headers */\n headers?: Record<string, string>;\n /** Whether endpoint is active */\n enabled: boolean;\n}\n\nexport interface AuthConfig {\n /** Get current user from request context */\n getUser: <T = unknown>(context: unknown) => Promise<T | null>;\n /** Check if user has admin privileges */\n isAdmin?: (user: unknown) => Promise<boolean>;\n /** Check if user can access specific device */\n canAccessDevice?: (user: unknown, deviceId: string) => Promise<boolean>;\n /** Device JWT secret (for device auth tokens) */\n deviceTokenSecret?: string;\n /** Device token expiration in seconds (default: 365 days) */\n deviceTokenExpiration?: number;\n}\n\nexport interface PushProviderConfig {\n provider: 'fcm' | 'mqtt' | 'websocket' | 'polling';\n\n // FCM configuration\n fcmCredentials?: string | Record<string, unknown>;\n fcmProjectId?: string;\n\n // MQTT configuration\n mqttUrl?: string;\n mqttUsername?: string;\n mqttPassword?: string;\n mqttTopicPrefix?: string;\n\n // WebSocket configuration\n wsPath?: string;\n wsPingInterval?: number;\n\n // Polling fallback\n pollingInterval?: number;\n}\n\nexport interface EnrollmentConfig {\n /** Auto-enroll devices with valid signature */\n autoEnroll?: boolean;\n /** HMAC secret for device signature verification */\n deviceSecret: string;\n /** Allowed enrollment methods */\n allowedMethods?: EnrollmentMethod[];\n /** Default policy for new devices */\n defaultPolicyId?: string;\n /** Default group for new devices */\n defaultGroupId?: string;\n /** Require manual approval for enrollment */\n requireApproval?: boolean;\n /** Custom enrollment validation */\n validate?: (request: EnrollmentRequest) => Promise<boolean>;\n}\n\n// ============================================\n// Adapter Interfaces\n// ============================================\n\nexport interface DatabaseAdapter {\n // Devices\n findDevice(id: string): Promise<Device | null>;\n findDeviceByEnrollmentId(enrollmentId: string): Promise<Device | null>;\n listDevices(filter?: DeviceFilter): Promise<DeviceListResult>;\n createDevice(data: CreateDeviceInput): Promise<Device>;\n updateDevice(id: string, data: UpdateDeviceInput): Promise<Device>;\n deleteDevice(id: string): Promise<void>;\n countDevices(filter?: DeviceFilter): Promise<number>;\n\n // Policies\n findPolicy(id: string): Promise<Policy | null>;\n findDefaultPolicy(): Promise<Policy | null>;\n listPolicies(): Promise<Policy[]>;\n createPolicy(data: CreatePolicyInput): Promise<Policy>;\n updatePolicy(id: string, data: UpdatePolicyInput): Promise<Policy>;\n deletePolicy(id: string): Promise<void>;\n\n // Applications\n findApplication(id: string): Promise<Application | null>;\n findApplicationByPackage(packageName: string, version?: string): Promise<Application | null>;\n listApplications(activeOnly?: boolean): Promise<Application[]>;\n createApplication(data: CreateApplicationInput): Promise<Application>;\n updateApplication(id: string, data: UpdateApplicationInput): Promise<Application>;\n deleteApplication(id: string): Promise<void>;\n\n // Commands\n findCommand(id: string): Promise<Command | null>;\n listCommands(filter?: CommandFilter): Promise<Command[]>;\n createCommand(data: SendCommandInput): Promise<Command>;\n updateCommand(id: string, data: Partial<Command>): Promise<Command | null>;\n getPendingCommands(deviceId: string): Promise<Command[]>;\n\n // Events\n createEvent(event: Omit<MDMEvent, 'id' | 'createdAt'>): Promise<MDMEvent>;\n listEvents(filter?: EventFilter): Promise<MDMEvent[]>;\n\n // Groups\n findGroup(id: string): Promise<Group | null>;\n listGroups(): Promise<Group[]>;\n createGroup(data: CreateGroupInput): Promise<Group>;\n updateGroup(id: string, data: UpdateGroupInput): Promise<Group>;\n deleteGroup(id: string): Promise<void>;\n listDevicesInGroup(groupId: string): Promise<Device[]>;\n addDeviceToGroup(deviceId: string, groupId: string): Promise<void>;\n removeDeviceFromGroup(deviceId: string, groupId: string): Promise<void>;\n getDeviceGroups(deviceId: string): Promise<Group[]>;\n\n // Push Tokens\n findPushToken(deviceId: string, provider: string): Promise<PushToken | null>;\n upsertPushToken(data: RegisterPushTokenInput): Promise<PushToken>;\n deletePushToken(deviceId: string, provider?: string): Promise<void>;\n\n // App Versions (optional - for version tracking)\n listAppVersions?(packageName: string): Promise<AppVersion[]>;\n createAppVersion?(data: Omit<AppVersion, 'id' | 'createdAt'>): Promise<AppVersion>;\n setMinimumVersion?(packageName: string, versionCode: number): Promise<void>;\n getMinimumVersion?(packageName: string): Promise<AppVersion | null>;\n\n // Rollback History (optional)\n createRollback?(data: CreateAppRollbackInput): Promise<AppRollback>;\n updateRollback?(id: string, data: Partial<AppRollback>): Promise<AppRollback>;\n listRollbacks?(filter?: { deviceId?: string; packageName?: string }): Promise<AppRollback[]>;\n\n // Group Hierarchy (optional)\n getGroupChildren?(parentId: string | null): Promise<Group[]>;\n getGroupAncestors?(groupId: string): Promise<Group[]>;\n getGroupDescendants?(groupId: string): Promise<Group[]>;\n getGroupTree?(rootId?: string): Promise<GroupTreeNode[]>;\n getGroupEffectivePolicy?(groupId: string): Promise<Policy | null>;\n moveGroup?(groupId: string, newParentId: string | null): Promise<Group>;\n getGroupHierarchyStats?(): Promise<GroupHierarchyStats>;\n\n // Tenants (optional - for multi-tenancy)\n findTenant?(id: string): Promise<Tenant | null>;\n findTenantBySlug?(slug: string): Promise<Tenant | null>;\n listTenants?(filter?: TenantFilter): Promise<TenantListResult>;\n createTenant?(data: CreateTenantInput): Promise<Tenant>;\n updateTenant?(id: string, data: UpdateTenantInput): Promise<Tenant>;\n deleteTenant?(id: string): Promise<void>;\n getTenantStats?(tenantId: string): Promise<TenantStats>;\n\n // Users (optional - for RBAC)\n findUser?(id: string): Promise<User | null>;\n findUserByEmail?(email: string, tenantId?: string): Promise<User | null>;\n listUsers?(filter?: UserFilter): Promise<UserListResult>;\n createUser?(data: CreateUserInput): Promise<User>;\n updateUser?(id: string, data: UpdateUserInput): Promise<User>;\n deleteUser?(id: string): Promise<void>;\n\n // Roles (optional - for RBAC)\n findRole?(id: string): Promise<Role | null>;\n listRoles?(tenantId?: string): Promise<Role[]>;\n createRole?(data: CreateRoleInput): Promise<Role>;\n updateRole?(id: string, data: UpdateRoleInput): Promise<Role>;\n deleteRole?(id: string): Promise<void>;\n assignRoleToUser?(userId: string, roleId: string): Promise<void>;\n removeRoleFromUser?(userId: string, roleId: string): Promise<void>;\n getUserRoles?(userId: string): Promise<Role[]>;\n\n // Audit Logs (optional - for compliance)\n createAuditLog?(data: CreateAuditLogInput): Promise<AuditLog>;\n listAuditLogs?(filter?: AuditLogFilter): Promise<AuditLogListResult>;\n deleteAuditLogs?(filter: { olderThan?: Date; tenantId?: string }): Promise<number>;\n\n // Scheduled Tasks (optional - for scheduling)\n findScheduledTask?(id: string): Promise<ScheduledTask | null>;\n listScheduledTasks?(filter?: ScheduledTaskFilter): Promise<ScheduledTaskListResult>;\n createScheduledTask?(data: CreateScheduledTaskInput): Promise<ScheduledTask>;\n updateScheduledTask?(id: string, data: UpdateScheduledTaskInput): Promise<ScheduledTask>;\n deleteScheduledTask?(id: string): Promise<void>;\n getUpcomingTasks?(hours: number): Promise<ScheduledTask[]>;\n createTaskExecution?(data: { taskId: string }): Promise<TaskExecution>;\n updateTaskExecution?(id: string, data: Partial<TaskExecution>): Promise<TaskExecution>;\n listTaskExecutions?(taskId: string, limit?: number): Promise<TaskExecution[]>;\n\n // Message Queue (optional - for persistent messaging)\n enqueueMessage?(data: EnqueueMessageInput): Promise<QueuedMessage>;\n dequeueMessages?(deviceId: string, limit?: number): Promise<QueuedMessage[]>;\n peekMessages?(deviceId: string, limit?: number): Promise<QueuedMessage[]>;\n acknowledgeMessage?(messageId: string): Promise<void>;\n failMessage?(messageId: string, error: string): Promise<void>;\n retryFailedMessages?(maxAttempts?: number): Promise<number>;\n purgeExpiredMessages?(): Promise<number>;\n getQueueStats?(tenantId?: string): Promise<QueueStats>;\n\n // Plugin Storage (optional)\n getPluginValue?(pluginName: string, key: string): Promise<unknown | null>;\n setPluginValue?(pluginName: string, key: string, value: unknown): Promise<void>;\n deletePluginValue?(pluginName: string, key: string): Promise<void>;\n listPluginKeys?(pluginName: string, prefix?: string): Promise<string[]>;\n clearPluginData?(pluginName: string): Promise<void>;\n\n // Dashboard (optional - for analytics)\n getDashboardStats?(tenantId?: string): Promise<DashboardStats>;\n getDeviceStatusBreakdown?(tenantId?: string): Promise<DeviceStatusBreakdown>;\n getEnrollmentTrend?(days: number, tenantId?: string): Promise<EnrollmentTrendPoint[]>;\n getCommandSuccessRates?(tenantId?: string): Promise<CommandSuccessRates>;\n getAppInstallationSummary?(tenantId?: string): Promise<AppInstallationSummary>;\n\n // Transactions (optional)\n transaction?<T>(fn: () => Promise<T>): Promise<T>;\n}\n\nexport interface PushAdapter {\n /** Send push message to a device */\n send(deviceId: string, message: PushMessage): Promise<PushResult>;\n /** Send push message to multiple devices */\n sendBatch(deviceIds: string[], message: PushMessage): Promise<PushBatchResult>;\n /** Register device push token */\n registerToken?(deviceId: string, token: string): Promise<void>;\n /** Unregister device push token */\n unregisterToken?(deviceId: string): Promise<void>;\n /** Subscribe device to topic */\n subscribe?(deviceId: string, topic: string): Promise<void>;\n /** Unsubscribe device from topic */\n unsubscribe?(deviceId: string, topic: string): Promise<void>;\n}\n\nexport interface PushMessage {\n type: string;\n payload?: Record<string, unknown>;\n priority?: 'high' | 'normal';\n ttl?: number;\n collapseKey?: string;\n}\n\nexport interface PushResult {\n success: boolean;\n messageId?: string;\n error?: string;\n}\n\nexport interface PushBatchResult {\n successCount: number;\n failureCount: number;\n results: Array<{ deviceId: string; result: PushResult }>;\n}\n\n// ============================================\n// Plugin Interface\n// ============================================\n\nexport interface MDMPlugin {\n /** Unique plugin name */\n name: string;\n /** Plugin version */\n version: string;\n\n /** Called when MDM is initialized */\n onInit?(mdm: MDMInstance): Promise<void>;\n\n /** Called when MDM is destroyed */\n onDestroy?(): Promise<void>;\n\n /** Additional routes to mount */\n routes?: PluginRoute[];\n\n /** Middleware to apply to all routes */\n middleware?: PluginMiddleware[];\n\n /** Extend enrollment process */\n onEnroll?(device: Device, request: EnrollmentRequest): Promise<void>;\n\n /** Extend device processing */\n onDeviceEnrolled?(device: Device): Promise<void>;\n onDeviceUnenrolled?(device: Device): Promise<void>;\n onHeartbeat?(device: Device, heartbeat: Heartbeat): Promise<void>;\n\n /** Extend policy processing */\n policySchema?: Record<string, unknown>;\n validatePolicy?(settings: PolicySettings): Promise<{ valid: boolean; errors?: string[] }>;\n applyPolicy?(device: Device, policy: Policy): Promise<void>;\n\n /** Extend command processing */\n commandTypes?: CommandType[];\n executeCommand?(device: Device, command: Command): Promise<CommandResult>;\n}\n\nexport interface PluginRoute {\n method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\n path: string;\n handler: (context: unknown) => Promise<unknown>;\n auth?: boolean;\n admin?: boolean;\n}\n\nexport type PluginMiddleware = (\n context: unknown,\n next: () => Promise<unknown>\n) => Promise<unknown>;\n\n// ============================================\n// MDM Instance Interface\n// ============================================\n\nexport interface WebhookManager {\n /** Deliver an event to all matching webhook endpoints */\n deliver<T>(event: MDMEvent<T>): Promise<WebhookDeliveryResult[]>;\n /** Add a webhook endpoint at runtime */\n addEndpoint(endpoint: WebhookEndpoint): void;\n /** Remove a webhook endpoint */\n removeEndpoint(endpointId: string): void;\n /** Update a webhook endpoint */\n updateEndpoint(endpointId: string, updates: Partial<WebhookEndpoint>): void;\n /** Get all configured endpoints */\n getEndpoints(): WebhookEndpoint[];\n /** Test a webhook endpoint with a test payload */\n testEndpoint(endpointId: string): Promise<WebhookDeliveryResult>;\n}\n\nexport interface WebhookDeliveryResult {\n endpointId: string;\n success: boolean;\n statusCode?: number;\n error?: string;\n retryCount: number;\n deliveredAt?: Date;\n}\n\nexport interface MDMInstance {\n /** Device management */\n devices: DeviceManager;\n /** Policy management */\n policies: PolicyManager;\n /** Application management */\n apps: ApplicationManager;\n /** Command management */\n commands: CommandManager;\n /** Group management */\n groups: GroupManager;\n\n /** Tenant management (if multi-tenancy enabled) */\n tenants?: TenantManager;\n /** Authorization management (RBAC) */\n authorization?: AuthorizationManager;\n /** Audit logging */\n audit?: AuditManager;\n /** Scheduled task management */\n schedules?: ScheduleManager;\n /** Persistent message queue */\n messageQueue?: MessageQueueManager;\n /** Dashboard analytics */\n dashboard?: DashboardManager;\n /** Plugin storage */\n pluginStorage?: PluginStorageAdapter;\n\n /** Push notification service */\n push: PushAdapter;\n\n /** Webhook delivery (if configured) */\n webhooks?: WebhookManager;\n\n /** Database adapter */\n db: DatabaseAdapter;\n\n /** Structured logger. Already scoped to the `openmdm` namespace. Plugins should call `.child({...})` to scope further. */\n logger: Logger;\n\n /** Configuration */\n config: MDMConfig;\n\n /** Subscribe to events */\n on<T extends EventType>(event: T, handler: EventHandler<T>): () => void;\n /** Emit an event */\n emit<T extends EventType>(event: T, data: EventPayloadMap[T]): Promise<void>;\n\n /** Process device enrollment */\n enroll(request: EnrollmentRequest): Promise<EnrollmentResponse>;\n /** Process device heartbeat */\n processHeartbeat(deviceId: string, heartbeat: Heartbeat): Promise<void>;\n /** Verify device token */\n verifyDeviceToken(token: string): Promise<{ deviceId: string } | null>;\n\n /** Get loaded plugins */\n getPlugins(): MDMPlugin[];\n /** Get plugin by name */\n getPlugin(name: string): MDMPlugin | undefined;\n}\n\n// ============================================\n// Manager Interfaces\n// ============================================\n\nexport interface DeviceManager {\n get(id: string): Promise<Device | null>;\n getByEnrollmentId(enrollmentId: string): Promise<Device | null>;\n list(filter?: DeviceFilter): Promise<DeviceListResult>;\n create(data: CreateDeviceInput): Promise<Device>;\n update(id: string, data: UpdateDeviceInput): Promise<Device>;\n delete(id: string): Promise<void>;\n assignPolicy(deviceId: string, policyId: string | null): Promise<Device>;\n addToGroup(deviceId: string, groupId: string): Promise<void>;\n removeFromGroup(deviceId: string, groupId: string): Promise<void>;\n getGroups(deviceId: string): Promise<Group[]>;\n sendCommand(deviceId: string, input: Omit<SendCommandInput, 'deviceId'>): Promise<Command>;\n sync(deviceId: string): Promise<Command>;\n reboot(deviceId: string): Promise<Command>;\n lock(deviceId: string, message?: string): Promise<Command>;\n wipe(deviceId: string, preserveData?: boolean): Promise<Command>;\n}\n\nexport interface PolicyManager {\n get(id: string): Promise<Policy | null>;\n getDefault(): Promise<Policy | null>;\n list(): Promise<Policy[]>;\n create(data: CreatePolicyInput): Promise<Policy>;\n update(id: string, data: UpdatePolicyInput): Promise<Policy>;\n delete(id: string): Promise<void>;\n setDefault(id: string): Promise<Policy>;\n getDevices(policyId: string): Promise<Device[]>;\n applyToDevice(policyId: string, deviceId: string): Promise<void>;\n}\n\nexport interface ApplicationManager {\n get(id: string): Promise<Application | null>;\n getByPackage(packageName: string, version?: string): Promise<Application | null>;\n list(activeOnly?: boolean): Promise<Application[]>;\n register(data: CreateApplicationInput): Promise<Application>;\n update(id: string, data: UpdateApplicationInput): Promise<Application>;\n delete(id: string): Promise<void>;\n activate(id: string): Promise<Application>;\n deactivate(id: string): Promise<Application>;\n deploy(packageName: string, target: DeployTarget): Promise<void>;\n installOnDevice(packageName: string, deviceId: string, version?: string): Promise<Command>;\n uninstallFromDevice(packageName: string, deviceId: string): Promise<Command>;\n}\n\nexport interface CommandManager {\n get(id: string): Promise<Command | null>;\n list(filter?: CommandFilter): Promise<Command[]>;\n send(input: SendCommandInput): Promise<Command>;\n cancel(id: string): Promise<Command>;\n acknowledge(id: string): Promise<Command>;\n complete(id: string, result: CommandResult): Promise<Command>;\n fail(id: string, error: string): Promise<Command>;\n getPending(deviceId: string): Promise<Command[]>;\n}\n\nexport interface GroupManager {\n // Basic CRUD operations\n get(id: string): Promise<Group | null>;\n list(): Promise<Group[]>;\n create(data: CreateGroupInput): Promise<Group>;\n update(id: string, data: UpdateGroupInput): Promise<Group>;\n delete(id: string): Promise<void>;\n\n // Device management\n getDevices(groupId: string): Promise<Device[]>;\n addDevice(groupId: string, deviceId: string): Promise<void>;\n removeDevice(groupId: string, deviceId: string): Promise<void>;\n\n // Hierarchy operations\n getChildren(groupId: string): Promise<Group[]>;\n getTree(rootId?: string): Promise<GroupTreeNode[]>;\n getAncestors(groupId: string): Promise<Group[]>;\n getDescendants(groupId: string): Promise<Group[]>;\n move(groupId: string, newParentId: string | null): Promise<Group>;\n getEffectivePolicy(groupId: string): Promise<Policy | null>;\n getHierarchyStats(): Promise<GroupHierarchyStats>;\n}\n\n// ============================================\n// Group Hierarchy Types\n// ============================================\n\nexport interface GroupTreeNode extends Group {\n children: GroupTreeNode[];\n depth: number;\n path: string[];\n effectivePolicyId?: string | null;\n}\n\nexport interface GroupHierarchyStats {\n totalGroups: number;\n maxDepth: number;\n groupsWithDevices: number;\n groupsWithPolicies: number;\n}\n\n// ============================================\n// Tenant Types (Multi-tenancy)\n// ============================================\n\nexport type TenantStatus = 'active' | 'suspended' | 'pending';\n\nexport interface Tenant {\n id: string;\n name: string;\n slug: string;\n status: TenantStatus;\n settings?: TenantSettings | null;\n metadata?: Record<string, unknown> | null;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface TenantSettings {\n maxDevices?: number;\n maxUsers?: number;\n features?: string[];\n branding?: {\n logo?: string;\n primaryColor?: string;\n };\n}\n\nexport interface CreateTenantInput {\n name: string;\n slug: string;\n settings?: TenantSettings;\n metadata?: Record<string, unknown>;\n}\n\nexport interface UpdateTenantInput {\n name?: string;\n slug?: string;\n status?: TenantStatus;\n settings?: TenantSettings;\n metadata?: Record<string, unknown>;\n}\n\nexport interface TenantFilter {\n status?: TenantStatus;\n search?: string;\n limit?: number;\n offset?: number;\n}\n\nexport interface TenantListResult {\n tenants: Tenant[];\n total: number;\n limit: number;\n offset: number;\n}\n\nexport interface TenantStats {\n deviceCount: number;\n userCount: number;\n policyCount: number;\n appCount: number;\n}\n\n// ============================================\n// RBAC Types (Role-Based Access Control)\n// ============================================\n\nexport type PermissionAction = 'create' | 'read' | 'update' | 'delete' | 'manage' | '*';\nexport type PermissionResource = 'devices' | 'policies' | 'apps' | 'groups' | 'commands' | 'users' | 'roles' | 'tenants' | 'audit' | '*';\n\nexport interface Permission {\n action: PermissionAction;\n resource: PermissionResource;\n resourceId?: string;\n}\n\nexport interface Role {\n id: string;\n tenantId?: string | null;\n name: string;\n description?: string | null;\n permissions: Permission[];\n isSystem: boolean;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface CreateRoleInput {\n tenantId?: string;\n name: string;\n description?: string;\n permissions: Permission[];\n}\n\nexport interface UpdateRoleInput {\n name?: string;\n description?: string;\n permissions?: Permission[];\n}\n\nexport interface User {\n id: string;\n tenantId?: string | null;\n email: string;\n name?: string | null;\n status: 'active' | 'inactive' | 'pending';\n metadata?: Record<string, unknown> | null;\n lastLoginAt?: Date | null;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface UserWithRoles extends User {\n roles: Role[];\n}\n\nexport interface CreateUserInput {\n tenantId?: string;\n email: string;\n name?: string;\n status?: 'active' | 'inactive' | 'pending';\n metadata?: Record<string, unknown>;\n}\n\nexport interface UpdateUserInput {\n email?: string;\n name?: string;\n status?: 'active' | 'inactive' | 'pending';\n metadata?: Record<string, unknown>;\n}\n\nexport interface UserFilter {\n tenantId?: string;\n status?: 'active' | 'inactive' | 'pending';\n search?: string;\n limit?: number;\n offset?: number;\n}\n\nexport interface UserListResult {\n users: User[];\n total: number;\n limit: number;\n offset: number;\n}\n\n// ============================================\n// Audit Types\n// ============================================\n\nexport type AuditAction = 'create' | 'read' | 'update' | 'delete' | 'login' | 'logout' | 'enroll' | 'unenroll' | 'command' | 'export' | 'import' | 'custom';\n\nexport interface AuditLog {\n id: string;\n tenantId?: string | null;\n userId?: string | null;\n action: AuditAction;\n resource: string;\n resourceId?: string | null;\n status: 'success' | 'failure';\n error?: string | null;\n details?: Record<string, unknown> | null;\n ipAddress?: string | null;\n userAgent?: string | null;\n createdAt: Date;\n}\n\nexport interface CreateAuditLogInput {\n tenantId?: string;\n userId?: string;\n action: AuditAction;\n resource: string;\n resourceId?: string;\n status?: 'success' | 'failure';\n error?: string;\n details?: Record<string, unknown>;\n ipAddress?: string;\n userAgent?: string;\n}\n\nexport interface AuditConfig {\n enabled: boolean;\n retentionDays?: number;\n skipReadOperations?: boolean;\n logActions?: AuditAction[];\n logResources?: string[];\n}\n\nexport interface AuditSummary {\n totalLogs: number;\n byAction: Record<AuditAction, number>;\n byResource: Record<string, number>;\n byStatus: { success: number; failure: number };\n topUsers: Array<{ userId: string; count: number }>;\n recentFailures: AuditLog[];\n}\n\nexport interface AuditLogFilter {\n tenantId?: string;\n userId?: string;\n action?: string;\n resource?: string;\n resourceId?: string;\n startDate?: Date;\n endDate?: Date;\n limit?: number;\n offset?: number;\n}\n\nexport interface AuditLogListResult {\n logs: AuditLog[];\n total: number;\n limit: number;\n offset: number;\n}\n\n// ============================================\n// Schedule Types\n// ============================================\n\nexport type TaskType = 'command' | 'policy_update' | 'app_install' | 'maintenance' | 'custom';\nexport type ScheduledTaskStatus = 'active' | 'paused' | 'completed' | 'failed';\n\nexport interface MaintenanceWindow {\n daysOfWeek: number[];\n startTime: string;\n endTime: string;\n timezone: string;\n}\n\nexport interface TaskSchedule {\n type: 'once' | 'recurring' | 'window';\n executeAt?: Date;\n cron?: string;\n window?: MaintenanceWindow;\n}\n\nexport interface ScheduledTask {\n id: string;\n tenantId?: string | null;\n name: string;\n description?: string | null;\n taskType: TaskType;\n schedule: TaskSchedule;\n target?: DeployTarget;\n payload?: Record<string, unknown> | null;\n status: ScheduledTaskStatus;\n nextRunAt?: Date | null;\n lastRunAt?: Date | null;\n maxRetries: number;\n retryCount: number;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface CreateScheduledTaskInput {\n tenantId?: string;\n name: string;\n description?: string;\n taskType: TaskType;\n schedule: TaskSchedule;\n target?: DeployTarget;\n payload?: Record<string, unknown>;\n maxRetries?: number;\n}\n\nexport interface UpdateScheduledTaskInput {\n name?: string;\n description?: string;\n schedule?: TaskSchedule;\n target?: DeployTarget;\n payload?: Record<string, unknown>;\n status?: ScheduledTaskStatus;\n maxRetries?: number;\n}\n\nexport interface ScheduledTaskFilter {\n tenantId?: string;\n taskType?: TaskType | TaskType[];\n status?: ScheduledTaskStatus | ScheduledTaskStatus[];\n limit?: number;\n offset?: number;\n}\n\nexport interface ScheduledTaskListResult {\n tasks: ScheduledTask[];\n total: number;\n limit: number;\n offset: number;\n}\n\nexport interface TaskExecution {\n id: string;\n taskId: string;\n status: 'running' | 'completed' | 'failed';\n startedAt: Date;\n completedAt?: Date | null;\n devicesProcessed: number;\n devicesSucceeded: number;\n devicesFailed: number;\n error?: string | null;\n details?: Record<string, unknown> | null;\n}\n\n// ============================================\n// Message Queue Types\n// ============================================\n\nexport type QueueMessageStatus = 'pending' | 'processing' | 'delivered' | 'failed' | 'expired';\n\nexport interface QueuedMessage {\n id: string;\n tenantId?: string | null;\n deviceId: string;\n messageType: string;\n payload: Record<string, unknown>;\n priority: 'high' | 'normal' | 'low';\n status: QueueMessageStatus;\n attempts: number;\n maxAttempts: number;\n lastAttemptAt?: Date | null;\n lastError?: string | null;\n expiresAt?: Date | null;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface EnqueueMessageInput {\n tenantId?: string;\n deviceId: string;\n messageType: string;\n payload: Record<string, unknown>;\n priority?: 'high' | 'normal' | 'low';\n maxAttempts?: number;\n ttlSeconds?: number;\n}\n\nexport interface QueueStats {\n pending: number;\n processing: number;\n delivered: number;\n failed: number;\n expired: number;\n byDevice: Record<string, number>;\n oldestPending?: Date;\n}\n\n// ============================================\n// Dashboard Types\n// ============================================\n\nexport interface DashboardStats {\n devices: {\n total: number;\n enrolled: number;\n active: number;\n blocked: number;\n pending: number;\n };\n policies: {\n total: number;\n deployed: number;\n };\n applications: {\n total: number;\n deployed: number;\n };\n commands: {\n pendingCount: number;\n last24hTotal: number;\n last24hSuccess: number;\n last24hFailed: number;\n };\n groups: {\n total: number;\n withDevices: number;\n };\n}\n\nexport interface DeviceStatusBreakdown {\n byStatus: Record<DeviceStatus, number>;\n byOs: Record<string, number>;\n byManufacturer: Record<string, number>;\n byModel: Record<string, number>;\n}\n\nexport interface EnrollmentTrendPoint {\n date: Date;\n enrolled: number;\n unenrolled: number;\n netChange: number;\n totalDevices: number;\n}\n\nexport interface CommandSuccessRates {\n overall: {\n total: number;\n completed: number;\n failed: number;\n successRate: number;\n };\n byType: Record<string, {\n total: number;\n completed: number;\n failed: number;\n successRate: number;\n avgExecutionTimeMs?: number;\n }>;\n last24h: {\n total: number;\n completed: number;\n failed: number;\n pending: number;\n };\n}\n\nexport interface AppInstallationSummary {\n total: number;\n byStatus: Record<string, number>;\n recentFailures: Array<{\n packageName: string;\n deviceId: string;\n error: string;\n timestamp: Date;\n }>;\n topInstalled: Array<{\n packageName: string;\n name: string;\n installedCount: number;\n }>;\n}\n\n// ============================================\n// Plugin Storage Types\n// ============================================\n\nexport interface PluginStorageAdapter {\n get<T>(pluginName: string, key: string): Promise<T | null>;\n set<T>(pluginName: string, key: string, value: T): Promise<void>;\n delete(pluginName: string, key: string): Promise<void>;\n list(pluginName: string, prefix?: string): Promise<string[]>;\n clear(pluginName: string): Promise<void>;\n}\n\nexport interface PluginStorageEntry {\n pluginName: string;\n key: string;\n value: unknown;\n createdAt: Date;\n updatedAt: Date;\n}\n\n// ============================================\n// Enterprise Manager Interfaces\n// ============================================\n\nexport interface TenantManager {\n get(id: string): Promise<Tenant | null>;\n getBySlug(slug: string): Promise<Tenant | null>;\n list(filter?: TenantFilter): Promise<TenantListResult>;\n create(data: CreateTenantInput): Promise<Tenant>;\n update(id: string, data: UpdateTenantInput): Promise<Tenant>;\n delete(id: string, cascade?: boolean): Promise<void>;\n getStats(tenantId: string): Promise<TenantStats>;\n activate(id: string): Promise<Tenant>;\n deactivate(id: string): Promise<Tenant>;\n}\n\nexport interface AuthorizationManager {\n createRole(data: CreateRoleInput): Promise<Role>;\n getRole(id: string): Promise<Role | null>;\n listRoles(tenantId?: string): Promise<Role[]>;\n updateRole(id: string, data: UpdateRoleInput): Promise<Role>;\n deleteRole(id: string): Promise<void>;\n createUser(data: CreateUserInput): Promise<User>;\n getUser(id: string): Promise<UserWithRoles | null>;\n getUserByEmail(email: string, tenantId?: string): Promise<UserWithRoles | null>;\n listUsers(filter?: UserFilter): Promise<UserListResult>;\n updateUser(id: string, data: UpdateUserInput): Promise<User>;\n deleteUser(id: string): Promise<void>;\n assignRole(userId: string, roleId: string): Promise<void>;\n removeRole(userId: string, roleId: string): Promise<void>;\n getUserRoles(userId: string): Promise<Role[]>;\n can(userId: string, action: PermissionAction, resource: PermissionResource, resourceId?: string): Promise<boolean>;\n canAny(userId: string, permissions: Array<{ action: PermissionAction; resource: PermissionResource }>): Promise<boolean>;\n requirePermission(userId: string, action: PermissionAction, resource: PermissionResource, resourceId?: string): Promise<void>;\n isAdmin(userId: string): Promise<boolean>;\n}\n\nexport interface AuditManager {\n log(entry: CreateAuditLogInput): Promise<AuditLog>;\n list(filter?: AuditLogFilter): Promise<AuditLogListResult>;\n getByResource(resource: string, resourceId: string): Promise<AuditLog[]>;\n getByUser(userId: string, filter?: AuditLogFilter): Promise<AuditLogListResult>;\n export(filter: AuditLogFilter, format: 'json' | 'csv'): Promise<string>;\n purge(olderThanDays?: number): Promise<number>;\n getSummary(tenantId?: string, days?: number): Promise<AuditSummary>;\n}\n\nexport interface ScheduleManager {\n get(id: string): Promise<ScheduledTask | null>;\n list(filter?: ScheduledTaskFilter): Promise<ScheduledTaskListResult>;\n create(data: CreateScheduledTaskInput): Promise<ScheduledTask>;\n update(id: string, data: UpdateScheduledTaskInput): Promise<ScheduledTask>;\n delete(id: string): Promise<void>;\n pause(id: string): Promise<ScheduledTask>;\n resume(id: string): Promise<ScheduledTask>;\n runNow(id: string): Promise<TaskExecution>;\n getUpcoming(hours: number): Promise<ScheduledTask[]>;\n getExecutions(taskId: string, limit?: number): Promise<TaskExecution[]>;\n calculateNextRun(schedule: TaskSchedule): Date | null;\n}\n\nexport interface MessageQueueManager {\n enqueue(message: EnqueueMessageInput): Promise<QueuedMessage>;\n enqueueBatch(messages: EnqueueMessageInput[]): Promise<QueuedMessage[]>;\n dequeue(deviceId: string, limit?: number): Promise<QueuedMessage[]>;\n acknowledge(messageId: string): Promise<void>;\n fail(messageId: string, error: string): Promise<void>;\n retryFailed(maxAttempts?: number): Promise<number>;\n purgeExpired(): Promise<number>;\n getStats(tenantId?: string): Promise<QueueStats>;\n peek(deviceId: string, limit?: number): Promise<QueuedMessage[]>;\n}\n\nexport interface DashboardManager {\n getStats(tenantId?: string): Promise<DashboardStats>;\n getDeviceStatusBreakdown(tenantId?: string): Promise<DeviceStatusBreakdown>;\n getEnrollmentTrend(days: number, tenantId?: string): Promise<EnrollmentTrendPoint[]>;\n getCommandSuccessRates(tenantId?: string): Promise<CommandSuccessRates>;\n getAppInstallationSummary(tenantId?: string): Promise<AppInstallationSummary>;\n}\n\n// ============================================\n// Event Handler Types\n// ============================================\n\nexport type EventHandler<T extends EventType> = (\n event: MDMEvent<EventPayloadMap[T]>\n) => Promise<void> | void;\n\nexport interface EventPayloadMap {\n 'device.enrolled': { device: Device };\n 'device.unenrolled': { device: Device; reason?: string };\n 'device.blocked': { device: Device; reason: string };\n 'device.heartbeat': { device: Device; heartbeat: Heartbeat };\n 'device.locationUpdated': { device: Device; location: DeviceLocation };\n 'device.statusChanged': { device: Device; oldStatus: DeviceStatus; newStatus: DeviceStatus };\n 'device.policyChanged': { device: Device; oldPolicyId?: string; newPolicyId?: string };\n 'app.installed': { device: Device; app: InstalledApp };\n 'app.uninstalled': { device: Device; packageName: string };\n 'app.updated': { device: Device; app: InstalledApp; oldVersion: string };\n 'app.crashed': { device: Device; packageName: string; error?: string };\n 'app.started': { device: Device; packageName: string };\n 'app.stopped': { device: Device; packageName: string };\n 'policy.applied': { device: Device; policy: Policy };\n 'policy.failed': { device: Device; policy: Policy; error: string };\n 'command.received': { device: Device; command: Command };\n 'command.acknowledged': { device: Device; command: Command };\n 'command.completed': { device: Device; command: Command; result: CommandResult };\n 'command.failed': { device: Device; command: Command; error: string };\n 'security.tamper': { device: Device; type: string; details?: unknown };\n 'security.rootDetected': { device: Device };\n 'security.screenLocked': { device: Device };\n 'security.screenUnlocked': { device: Device };\n custom: Record<string, unknown>;\n}\n\n// ============================================\n// Error Types\n// ============================================\n\nexport class MDMError extends Error {\n constructor(\n message: string,\n public code: string,\n public statusCode: number = 500,\n public details?: unknown\n ) {\n super(message);\n this.name = 'MDMError';\n }\n}\n\nexport class DeviceNotFoundError extends MDMError {\n constructor(deviceId: string) {\n super(`Device not found: ${deviceId}`, 'DEVICE_NOT_FOUND', 404);\n }\n}\n\nexport class PolicyNotFoundError extends MDMError {\n constructor(policyId: string) {\n super(`Policy not found: ${policyId}`, 'POLICY_NOT_FOUND', 404);\n }\n}\n\nexport class ApplicationNotFoundError extends MDMError {\n constructor(identifier: string) {\n super(`Application not found: ${identifier}`, 'APPLICATION_NOT_FOUND', 404);\n }\n}\n\nexport class CommandNotFoundError extends MDMError {\n constructor(commandId: string) {\n super(`Command not found: ${commandId}`, 'COMMAND_NOT_FOUND', 404);\n }\n}\n\nexport class TenantNotFoundError extends MDMError {\n constructor(identifier: string) {\n super(`Tenant not found: ${identifier}`, 'TENANT_NOT_FOUND', 404);\n }\n}\n\nexport class RoleNotFoundError extends MDMError {\n constructor(identifier: string) {\n super(`Role not found: ${identifier}`, 'ROLE_NOT_FOUND', 404);\n }\n}\n\nexport class GroupNotFoundError extends MDMError {\n constructor(identifier: string) {\n super(`Group not found: ${identifier}`, 'GROUP_NOT_FOUND', 404);\n }\n}\n\nexport class UserNotFoundError extends MDMError {\n constructor(identifier: string) {\n super(`User not found: ${identifier}`, 'USER_NOT_FOUND', 404);\n }\n}\n\nexport class EnrollmentError extends MDMError {\n constructor(message: string, details?: unknown) {\n super(message, 'ENROLLMENT_ERROR', 400, details);\n }\n}\n\nexport class AuthenticationError extends MDMError {\n constructor(message: string = 'Authentication required') {\n super(message, 'AUTHENTICATION_ERROR', 401);\n }\n}\n\nexport class AuthorizationError extends MDMError {\n constructor(message: string = 'Access denied') {\n super(message, 'AUTHORIZATION_ERROR', 403);\n }\n}\n\nexport class ValidationError extends MDMError {\n constructor(message: string, details?: unknown) {\n super(message, 'VALIDATION_ERROR', 400, details);\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openmdm/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Core MDM SDK - device management, policies, and commands",
|
|
5
5
|
"author": "OpenMDM Contributors",
|
|
6
6
|
"type": "module",
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
"tsup": "^8.0.0",
|
|
34
34
|
"typescript": "^5.5.0",
|
|
35
35
|
"vitest": "^2.0.0",
|
|
36
|
-
"@vitest/coverage-v8": "^2.0.0"
|
|
36
|
+
"@vitest/coverage-v8": "^2.0.0",
|
|
37
|
+
"@openmdm/client": "0.2.1"
|
|
37
38
|
},
|
|
38
39
|
"peerDependencies": {},
|
|
39
40
|
"keywords": [
|
package/src/index.ts
CHANGED
|
@@ -73,6 +73,7 @@ import type {
|
|
|
73
73
|
PluginStorageAdapter,
|
|
74
74
|
GroupTreeNode,
|
|
75
75
|
GroupHierarchyStats,
|
|
76
|
+
Logger,
|
|
76
77
|
} from './types';
|
|
77
78
|
import {
|
|
78
79
|
DeviceNotFoundError,
|
|
@@ -88,6 +89,7 @@ import { createScheduleManager } from './schedule';
|
|
|
88
89
|
import { createMessageQueueManager } from './queue';
|
|
89
90
|
import { createDashboardManager } from './dashboard';
|
|
90
91
|
import { createPluginStorageAdapter, createMemoryPluginStorageAdapter } from './plugin-storage';
|
|
92
|
+
import { createConsoleLogger, createSilentLogger } from './logger';
|
|
91
93
|
|
|
92
94
|
// Re-export all types
|
|
93
95
|
export * from './types';
|
|
@@ -95,6 +97,7 @@ export * from './schema';
|
|
|
95
97
|
export * from './agent-protocol';
|
|
96
98
|
export { createWebhookManager, verifyWebhookSignature } from './webhooks';
|
|
97
99
|
export type { WebhookPayload } from './webhooks';
|
|
100
|
+
export { createConsoleLogger, createSilentLogger } from './logger';
|
|
98
101
|
|
|
99
102
|
// Re-export enterprise manager factories
|
|
100
103
|
export { createTenantManager } from './tenant';
|
|
@@ -111,17 +114,36 @@ export { createPluginStorageAdapter, createMemoryPluginStorageAdapter, createPlu
|
|
|
111
114
|
export function createMDM(config: MDMConfig): MDMInstance {
|
|
112
115
|
const { database, push, enrollment, webhooks: webhooksConfig, plugins = [] } = config;
|
|
113
116
|
|
|
117
|
+
// Structured logger. Falls back to the console-backed default if
|
|
118
|
+
// the host doesn't pass one. Host code is expected to pass a real
|
|
119
|
+
// pino/winston instance in production.
|
|
120
|
+
const logger = config.logger ?? createConsoleLogger();
|
|
121
|
+
|
|
122
|
+
// Extract a stable message from an unknown thrown value so it
|
|
123
|
+
// survives JSON serialization into the log context. Error objects
|
|
124
|
+
// stringify to `{}` otherwise, which is the #1 cause of "we can't
|
|
125
|
+
// tell why this failed" in production logs.
|
|
126
|
+
const errorMessage = (err: unknown): string => {
|
|
127
|
+
if (err instanceof Error) return err.message;
|
|
128
|
+
if (typeof err === 'string') return err;
|
|
129
|
+
try {
|
|
130
|
+
return JSON.stringify(err);
|
|
131
|
+
} catch {
|
|
132
|
+
return String(err);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
114
136
|
// Event handlers registry
|
|
115
137
|
const eventHandlers = new Map<EventType, Set<EventHandler<EventType>>>();
|
|
116
138
|
|
|
117
139
|
// Create push adapter
|
|
118
140
|
const pushAdapter: PushAdapter = push
|
|
119
|
-
? createPushAdapter(push, database)
|
|
120
|
-
: createStubPushAdapter();
|
|
141
|
+
? createPushAdapter(push, database, logger)
|
|
142
|
+
: createStubPushAdapter(logger);
|
|
121
143
|
|
|
122
144
|
// Create webhook manager if configured
|
|
123
145
|
const webhookManager: WebhookManager | undefined = webhooksConfig
|
|
124
|
-
? createWebhookManager(webhooksConfig)
|
|
146
|
+
? createWebhookManager(webhooksConfig, logger)
|
|
125
147
|
: undefined;
|
|
126
148
|
|
|
127
149
|
// ============================================
|
|
@@ -205,13 +227,16 @@ export function createMDM(config: MDMConfig): MDMInstance {
|
|
|
205
227
|
payload: eventRecord.payload as Record<string, unknown>,
|
|
206
228
|
});
|
|
207
229
|
} catch (error) {
|
|
208
|
-
|
|
230
|
+
logger.error({ err: errorMessage(error), event }, 'Failed to persist event');
|
|
209
231
|
}
|
|
210
232
|
|
|
211
233
|
// Deliver webhooks (async, don't wait)
|
|
212
234
|
if (webhookManager) {
|
|
213
235
|
webhookManager.deliver(eventRecord).catch((error) => {
|
|
214
|
-
|
|
236
|
+
logger.error(
|
|
237
|
+
{ err: errorMessage(error), event },
|
|
238
|
+
'Webhook delivery error',
|
|
239
|
+
);
|
|
215
240
|
});
|
|
216
241
|
}
|
|
217
242
|
|
|
@@ -221,7 +246,10 @@ export function createMDM(config: MDMConfig): MDMInstance {
|
|
|
221
246
|
try {
|
|
222
247
|
await handler(eventRecord);
|
|
223
248
|
} catch (error) {
|
|
224
|
-
|
|
249
|
+
logger.error(
|
|
250
|
+
{ err: errorMessage(error), event },
|
|
251
|
+
'Event handler threw',
|
|
252
|
+
);
|
|
225
253
|
}
|
|
226
254
|
}
|
|
227
255
|
}
|
|
@@ -231,7 +259,7 @@ export function createMDM(config: MDMConfig): MDMInstance {
|
|
|
231
259
|
try {
|
|
232
260
|
await config.onEvent(eventRecord);
|
|
233
261
|
} catch (error) {
|
|
234
|
-
|
|
262
|
+
logger.error({ err: errorMessage(error) }, 'onEvent hook threw');
|
|
235
263
|
}
|
|
236
264
|
}
|
|
237
265
|
};
|
|
@@ -1193,6 +1221,7 @@ export function createMDM(config: MDMConfig): MDMInstance {
|
|
|
1193
1221
|
push: pushAdapter,
|
|
1194
1222
|
webhooks: webhookManager,
|
|
1195
1223
|
db: database,
|
|
1224
|
+
logger,
|
|
1196
1225
|
config,
|
|
1197
1226
|
on,
|
|
1198
1227
|
emit,
|
|
@@ -1217,11 +1246,11 @@ export function createMDM(config: MDMConfig): MDMInstance {
|
|
|
1217
1246
|
if (plugin.onInit) {
|
|
1218
1247
|
try {
|
|
1219
1248
|
await plugin.onInit(instance);
|
|
1220
|
-
|
|
1249
|
+
logger.info({ plugin: plugin.name }, 'Plugin initialized');
|
|
1221
1250
|
} catch (error) {
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1251
|
+
logger.error(
|
|
1252
|
+
{ plugin: plugin.name, err: errorMessage(error) },
|
|
1253
|
+
'Failed to initialize plugin',
|
|
1225
1254
|
);
|
|
1226
1255
|
}
|
|
1227
1256
|
}
|
|
@@ -1237,19 +1266,22 @@ export function createMDM(config: MDMConfig): MDMInstance {
|
|
|
1237
1266
|
|
|
1238
1267
|
function createPushAdapter(
|
|
1239
1268
|
config: MDMConfig['push'],
|
|
1240
|
-
database: MDMConfig['database']
|
|
1269
|
+
database: MDMConfig['database'],
|
|
1270
|
+
logger: Logger,
|
|
1241
1271
|
): PushAdapter {
|
|
1242
1272
|
if (!config) {
|
|
1243
|
-
return createStubPushAdapter();
|
|
1273
|
+
return createStubPushAdapter(logger);
|
|
1244
1274
|
}
|
|
1245
1275
|
|
|
1276
|
+
const pushLogger = logger.child({ component: 'push' });
|
|
1277
|
+
|
|
1246
1278
|
// The actual implementations will be provided by separate packages
|
|
1247
1279
|
// This is a base implementation that logs and stores tokens
|
|
1248
1280
|
return {
|
|
1249
1281
|
async send(deviceId: string, message: PushMessage): Promise<PushResult> {
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1282
|
+
pushLogger.debug(
|
|
1283
|
+
{ deviceId, type: message.type, payload: message.payload },
|
|
1284
|
+
'send',
|
|
1253
1285
|
);
|
|
1254
1286
|
|
|
1255
1287
|
// In production, this would be replaced by FCM/MQTT adapter
|
|
@@ -1260,8 +1292,9 @@ function createPushAdapter(
|
|
|
1260
1292
|
deviceIds: string[],
|
|
1261
1293
|
message: PushMessage
|
|
1262
1294
|
): Promise<PushBatchResult> {
|
|
1263
|
-
|
|
1264
|
-
|
|
1295
|
+
pushLogger.debug(
|
|
1296
|
+
{ count: deviceIds.length, type: message.type },
|
|
1297
|
+
'sendBatch',
|
|
1265
1298
|
);
|
|
1266
1299
|
|
|
1267
1300
|
const results = deviceIds.map((deviceId) => ({
|
|
@@ -1298,10 +1331,11 @@ function createPushAdapter(
|
|
|
1298
1331
|
};
|
|
1299
1332
|
}
|
|
1300
1333
|
|
|
1301
|
-
function createStubPushAdapter(): PushAdapter {
|
|
1334
|
+
function createStubPushAdapter(logger: Logger): PushAdapter {
|
|
1335
|
+
const stubLogger = logger.child({ component: 'push-stub' });
|
|
1302
1336
|
return {
|
|
1303
1337
|
async send(deviceId: string, message: PushMessage): Promise<PushResult> {
|
|
1304
|
-
|
|
1338
|
+
stubLogger.debug({ deviceId, type: message.type }, 'send (stub)');
|
|
1305
1339
|
return { success: true, messageId: 'stub' };
|
|
1306
1340
|
},
|
|
1307
1341
|
|
|
@@ -1309,8 +1343,9 @@ function createStubPushAdapter(): PushAdapter {
|
|
|
1309
1343
|
deviceIds: string[],
|
|
1310
1344
|
message: PushMessage
|
|
1311
1345
|
): Promise<PushBatchResult> {
|
|
1312
|
-
|
|
1313
|
-
|
|
1346
|
+
stubLogger.debug(
|
|
1347
|
+
{ count: deviceIds.length, type: message.type },
|
|
1348
|
+
'sendBatch (stub)',
|
|
1314
1349
|
);
|
|
1315
1350
|
return {
|
|
1316
1351
|
successCount: deviceIds.length,
|
|
@@ -1328,7 +1363,7 @@ function createStubPushAdapter(): PushAdapter {
|
|
|
1328
1363
|
// Utility Functions
|
|
1329
1364
|
// ============================================
|
|
1330
1365
|
|
|
1331
|
-
function verifyEnrollmentSignature(
|
|
1366
|
+
export function verifyEnrollmentSignature(
|
|
1332
1367
|
request: EnrollmentRequest,
|
|
1333
1368
|
secret: string
|
|
1334
1369
|
): boolean {
|
|
@@ -1338,11 +1373,21 @@ function verifyEnrollmentSignature(
|
|
|
1338
1373
|
return false;
|
|
1339
1374
|
}
|
|
1340
1375
|
|
|
1341
|
-
// Reconstruct the message that was signed
|
|
1342
|
-
//
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
const message =
|
|
1376
|
+
// Reconstruct the message that was signed. This must stay in lockstep with
|
|
1377
|
+
// @openmdm/client's generateEnrollmentSignature — any change here is a wire
|
|
1378
|
+
// break and must land in both places. A contract test in core/tests guards
|
|
1379
|
+
// the format and will fail on divergence.
|
|
1380
|
+
const message = [
|
|
1381
|
+
data.model,
|
|
1382
|
+
data.manufacturer,
|
|
1383
|
+
data.osVersion,
|
|
1384
|
+
data.serialNumber || '',
|
|
1385
|
+
data.imei || '',
|
|
1386
|
+
data.macAddress || '',
|
|
1387
|
+
data.androidId || '',
|
|
1388
|
+
data.method,
|
|
1389
|
+
data.timestamp,
|
|
1390
|
+
].join('|');
|
|
1346
1391
|
|
|
1347
1392
|
const expectedSignature = createHmac('sha256', secret)
|
|
1348
1393
|
.update(message)
|