@openmdm/core 0.4.0 → 0.6.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 CHANGED
@@ -1,7 +1,120 @@
1
1
  import { WebhookConfig, MDMEvent, WebhookEndpoint, EventType, DatabaseAdapter, TenantManager, AuthorizationManager, AuditConfig, AuditManager, ScheduleManager, MessageQueueManager, DashboardManager, PluginStorageAdapter, MDMConfig, MDMInstance } from './types.js';
2
- export { AppInstallationSummary, AppRollback, AppVersion, Application, ApplicationManager, ApplicationNotFoundError, AuditAction, AuditLog, AuditLogFilter, AuditLogListResult, AuditSummary, AuthConfig, AuthenticationError, AuthorizationError, Command, CommandFilter, CommandManager, CommandResult, CommandStatus, CommandSuccessRates, CommandType, CreateAppRollbackInput, CreateApplicationInput, CreateAuditLogInput, CreateDeviceInput, CreateGroupInput, CreatePolicyInput, CreateRoleInput, CreateScheduledTaskInput, CreateTenantInput, CreateUserInput, DashboardStats, DeployTarget, Device, DeviceFilter, DeviceListResult, DeviceLocation, DeviceManager, DeviceNotFoundError, DeviceStatus, DeviceStatusBreakdown, EnqueueMessageInput, EnrollmentConfig, EnrollmentError, EnrollmentMethod, EnrollmentRequest, EnrollmentResponse, EnrollmentTrendPoint, EventFilter, EventHandler, EventPayloadMap, Group, GroupHierarchyStats, GroupManager, GroupNotFoundError, GroupTreeNode, HardwareControl, Heartbeat, InstalledApp, MDMError, MDMPlugin, MaintenanceWindow, PasswordPolicy, Permission, PermissionAction, PermissionResource, PluginMiddleware, PluginRoute, PluginStorageEntry, Policy, PolicyApplication, PolicyManager, PolicyNotFoundError, PolicySettings, PushAdapter, PushBatchResult, PushConfig, PushMessage, PushProviderConfig, PushResult, PushToken, QueueMessageStatus, QueueStats, QueuedMessage, RegisterPushTokenInput, Role, RoleNotFoundError, ScheduledTask, ScheduledTaskFilter, ScheduledTaskListResult, ScheduledTaskStatus, SendCommandInput, StorageConfig, SystemUpdatePolicy, TaskExecution, TaskSchedule, TaskType, Tenant, TenantFilter, TenantListResult, TenantNotFoundError, TenantSettings, TenantStats, TenantStatus, TimeWindow, UpdateApplicationInput, UpdateDeviceInput, UpdateGroupInput, UpdatePolicyInput, UpdateRoleInput, UpdateScheduledTaskInput, UpdateTenantInput, UpdateUserInput, User, UserFilter, UserListResult, UserNotFoundError, UserWithRoles, ValidationError, VpnConfig, WebhookDeliveryResult, WebhookManager, WifiConfig } from './types.js';
2
+ export { AppInstallationSummary, AppRollback, AppVersion, Application, ApplicationManager, ApplicationNotFoundError, AuditAction, AuditLog, AuditLogFilter, AuditLogListResult, AuditSummary, AuthConfig, AuthenticationError, AuthorizationError, Command, CommandFilter, CommandManager, CommandNotFoundError, CommandResult, CommandStatus, CommandSuccessRates, CommandType, CreateAppRollbackInput, CreateApplicationInput, CreateAuditLogInput, CreateDeviceInput, CreateGroupInput, CreatePolicyInput, CreateRoleInput, CreateScheduledTaskInput, CreateTenantInput, CreateUserInput, DashboardStats, DeployTarget, Device, DeviceFilter, DeviceListResult, DeviceLocation, DeviceManager, DeviceNotFoundError, DeviceStatus, DeviceStatusBreakdown, EnqueueMessageInput, EnrollmentConfig, EnrollmentError, EnrollmentMethod, EnrollmentRequest, EnrollmentResponse, EnrollmentTrendPoint, EventFilter, EventHandler, EventPayloadMap, Group, GroupHierarchyStats, GroupManager, GroupNotFoundError, GroupTreeNode, HardwareControl, Heartbeat, InstalledApp, MDMError, MDMPlugin, MaintenanceWindow, PasswordPolicy, Permission, PermissionAction, PermissionResource, PluginMiddleware, PluginRoute, PluginStorageEntry, Policy, PolicyApplication, PolicyManager, PolicyNotFoundError, PolicySettings, PushAdapter, PushBatchResult, PushConfig, PushMessage, PushProviderConfig, PushResult, PushToken, QueueMessageStatus, QueueStats, QueuedMessage, RegisterPushTokenInput, Role, RoleNotFoundError, ScheduledTask, ScheduledTaskFilter, ScheduledTaskListResult, ScheduledTaskStatus, SendCommandInput, StorageConfig, SystemUpdatePolicy, TaskExecution, TaskSchedule, TaskType, Tenant, TenantFilter, TenantListResult, TenantNotFoundError, TenantSettings, TenantStats, TenantStatus, TimeWindow, UpdateApplicationInput, UpdateDeviceInput, UpdateGroupInput, UpdatePolicyInput, UpdateRoleInput, UpdateScheduledTaskInput, UpdateTenantInput, UpdateUserInput, User, UserFilter, UserListResult, UserNotFoundError, UserWithRoles, ValidationError, VpnConfig, WebhookDeliveryResult, WebhookManager, WifiConfig } from './types.js';
3
3
  export { ColumnDefinition, ColumnType, IndexDefinition, SchemaDefinition, TableDefinition, camelToSnake, getColumnNames, getPrimaryKey, getTableNames, mdmSchema, snakeToCamel, transformToCamelCase, transformToSnakeCase } from './schema.js';
4
4
 
5
+ /**
6
+ * OpenMDM Agent Wire Protocol v2.
7
+ *
8
+ * A unified response envelope for every `/agent/*` endpoint, plus the
9
+ * version-selection rules that let the server serve v1 and v2 clients
10
+ * simultaneously during a fleet rollout.
11
+ *
12
+ * ## Background
13
+ *
14
+ * Until now, agent-facing handlers returned either a bare JSON body
15
+ * on success or raised an `HTTPException(401|404|5xx)` on failure.
16
+ * The agent had to interpret five different HTTP status codes and
17
+ * infer what to do about each — which in practice meant "on auth
18
+ * error, wipe local enrollment state and re-enroll". That single
19
+ * ambiguity produced the auto-unenroll behavior we saw in production:
20
+ * a transient 401 or 404 was indistinguishable from "you are really
21
+ * unenrolled", so the agent self-destructed.
22
+ *
23
+ * ## Protocol v2
24
+ *
25
+ * Every agent-facing endpoint replies with HTTP 200 and a body of
26
+ * shape {@link AgentResponse}:
27
+ *
28
+ * ```json
29
+ * { "ok": true, "action": "none", "data": { ... } }
30
+ * { "ok": false, "action": "retry", "message": "..." }
31
+ * { "ok": false, "action": "reauth", "message": "..." }
32
+ * { "ok": false, "action": "unenroll", "message": "..." }
33
+ * ```
34
+ *
35
+ * - `ok` is the boolean the agent checks first.
36
+ * - `action` is the *only* field the agent reads to decide what to do
37
+ * next. There is exactly one handler per action on the client, so
38
+ * adding a new server response path is a matter of picking an
39
+ * existing action.
40
+ * - `data` carries the handler-specific payload (heartbeat response,
41
+ * policy update, etc.) on success.
42
+ * - `message` is a human-readable hint, for logs.
43
+ *
44
+ * HTTP 5xx is still used for real infrastructure failures (the Lambda
45
+ * timed out, the database connection dropped, etc.). v2 envelopes are
46
+ * reserved for *application-level* failures the agent can reason about.
47
+ *
48
+ * ## Versioning and rollout
49
+ *
50
+ * The agent opts into v2 by sending the header
51
+ * `X-Openmdm-Protocol: 2` on every request. When absent, the server
52
+ * falls back to the legacy v1 behavior — bare JSON on success,
53
+ * `HTTPException(401|404|…)` on failure — so a fleet still running
54
+ * older APKs keeps working during rollout.
55
+ *
56
+ * After the fleet has been upgraded, v1 can be dropped in a future
57
+ * major release by ignoring the header and always emitting v2.
58
+ */
59
+ /**
60
+ * Instruction the server gives the agent on how to react to this
61
+ * response. This is the entire client-side decision space.
62
+ *
63
+ * - `none`: happy path. The agent consumes `data` and continues.
64
+ * - `retry`: transient problem. The agent re-tries later without
65
+ * touching local state.
66
+ * - `reauth`: the agent's access token is no longer valid. It should
67
+ * call the refresh flow. It must NOT wipe enrollment state.
68
+ * - `unenroll`: the server-side record for this device is gone or
69
+ * blocked and the agent's credentials will never work again. The
70
+ * agent should stop making requests and surface this to the user.
71
+ * In Phase 2b this will be further softened: the agent will attempt
72
+ * a hardware-identity-based rebind before treating this as terminal.
73
+ */
74
+ type AgentAction = 'none' | 'retry' | 'reauth' | 'unenroll';
75
+ /**
76
+ * Unified response envelope for every `/agent/*` endpoint under
77
+ * protocol v2.
78
+ *
79
+ * Successful responses carry `data`; failure responses carry
80
+ * `message`. The envelope never carries both the happy-path payload
81
+ * and an error hint at the same time.
82
+ */
83
+ type AgentResponse<T = unknown> = {
84
+ ok: true;
85
+ action: 'none';
86
+ data: T;
87
+ } | {
88
+ ok: false;
89
+ action: Exclude<AgentAction, 'none'>;
90
+ message?: string;
91
+ };
92
+ /**
93
+ * HTTP header an agent sends to opt into protocol v2. Case-insensitive
94
+ * on the wire; use the constant to avoid typos.
95
+ */
96
+ declare const AGENT_PROTOCOL_HEADER = "X-Openmdm-Protocol";
97
+ /**
98
+ * Current wire-protocol version. Agents that send
99
+ * `X-Openmdm-Protocol: 2` get envelope responses. Absent or older
100
+ * values are served with the legacy flat shape.
101
+ */
102
+ declare const AGENT_PROTOCOL_V2 = "2";
103
+ /**
104
+ * Helper: build a success envelope.
105
+ */
106
+ declare function agentOk<T>(data: T): AgentResponse<T>;
107
+ /**
108
+ * Helper: build a failure envelope.
109
+ */
110
+ declare function agentFail(action: Exclude<AgentAction, 'none'>, message?: string): AgentResponse<never>;
111
+ /**
112
+ * Returns `true` iff the caller should be served protocol v2. The
113
+ * input is the value of the {@link AGENT_PROTOCOL_HEADER} header,
114
+ * which may be undefined.
115
+ */
116
+ declare function wantsAgentProtocolV2(headerValue: string | undefined | null): boolean;
117
+
5
118
  /**
6
119
  * OpenMDM Webhook Delivery System
7
120
  *
@@ -189,4 +302,4 @@ declare function parsePluginKey(key: string): {
189
302
  */
190
303
  declare function createMDM(config: MDMConfig): MDMInstance;
191
304
 
192
- export { AuditConfig, AuditManager, AuthorizationManager, DashboardManager, DatabaseAdapter, EventType, MDMConfig, MDMEvent, MDMInstance, MessageQueueManager, PluginStorageAdapter, ScheduleManager, TenantManager, WebhookConfig, WebhookEndpoint, type WebhookPayload, createAuditManager, createAuthorizationManager, createDashboardManager, createMDM, createMemoryPluginStorageAdapter, createMessageQueueManager, createPluginKey, createPluginStorageAdapter, createScheduleManager, createTenantManager, createWebhookManager, parsePluginKey, verifyWebhookSignature };
305
+ export { AGENT_PROTOCOL_HEADER, AGENT_PROTOCOL_V2, type AgentAction, type AgentResponse, AuditConfig, AuditManager, AuthorizationManager, DashboardManager, DatabaseAdapter, EventType, MDMConfig, MDMEvent, MDMInstance, MessageQueueManager, PluginStorageAdapter, ScheduleManager, TenantManager, WebhookConfig, WebhookEndpoint, type WebhookPayload, agentFail, agentOk, createAuditManager, createAuthorizationManager, createDashboardManager, createMDM, createMemoryPluginStorageAdapter, createMessageQueueManager, createPluginKey, createPluginStorageAdapter, createScheduleManager, createTenantManager, createWebhookManager, parsePluginKey, verifyWebhookSignature, wantsAgentProtocolV2 };
package/dist/index.js CHANGED
@@ -27,6 +27,11 @@ var ApplicationNotFoundError = class extends MDMError {
27
27
  super(`Application not found: ${identifier}`, "APPLICATION_NOT_FOUND", 404);
28
28
  }
29
29
  };
30
+ var CommandNotFoundError = class extends MDMError {
31
+ constructor(commandId) {
32
+ super(`Command not found: ${commandId}`, "COMMAND_NOT_FOUND", 404);
33
+ }
34
+ };
30
35
  var TenantNotFoundError = class extends MDMError {
31
36
  constructor(identifier) {
32
37
  super(`Tenant not found: ${identifier}`, "TENANT_NOT_FOUND", 404);
@@ -2021,6 +2026,19 @@ function transformToSnakeCase(obj) {
2021
2026
  return result;
2022
2027
  }
2023
2028
 
2029
+ // src/agent-protocol.ts
2030
+ var AGENT_PROTOCOL_HEADER = "X-Openmdm-Protocol";
2031
+ var AGENT_PROTOCOL_V2 = "2";
2032
+ function agentOk(data) {
2033
+ return { ok: true, action: "none", data };
2034
+ }
2035
+ function agentFail(action, message) {
2036
+ return { ok: false, action, message };
2037
+ }
2038
+ function wantsAgentProtocolV2(headerValue) {
2039
+ return headerValue === AGENT_PROTOCOL_V2;
2040
+ }
2041
+
2024
2042
  // src/index.ts
2025
2043
  function createMDM(config) {
2026
2044
  const { database, push, enrollment, webhooks: webhooksConfig, plugins = [] } = config;
@@ -2367,13 +2385,20 @@ function createMDM(config) {
2367
2385
  });
2368
2386
  },
2369
2387
  async cancel(id) {
2370
- return database.updateCommand(id, { status: "cancelled" });
2388
+ const command = await database.updateCommand(id, { status: "cancelled" });
2389
+ if (!command) {
2390
+ throw new CommandNotFoundError(id);
2391
+ }
2392
+ return command;
2371
2393
  },
2372
2394
  async acknowledge(id) {
2373
2395
  const command = await database.updateCommand(id, {
2374
2396
  status: "acknowledged",
2375
2397
  acknowledgedAt: /* @__PURE__ */ new Date()
2376
2398
  });
2399
+ if (!command) {
2400
+ throw new CommandNotFoundError(id);
2401
+ }
2377
2402
  const device = await database.findDevice(command.deviceId);
2378
2403
  if (device) {
2379
2404
  await emit("command.acknowledged", { device, command });
@@ -2386,6 +2411,9 @@ function createMDM(config) {
2386
2411
  result,
2387
2412
  completedAt: /* @__PURE__ */ new Date()
2388
2413
  });
2414
+ if (!command) {
2415
+ throw new CommandNotFoundError(id);
2416
+ }
2389
2417
  const device = await database.findDevice(command.deviceId);
2390
2418
  if (device) {
2391
2419
  await emit("command.completed", { device, command, result });
@@ -2398,6 +2426,9 @@ function createMDM(config) {
2398
2426
  error,
2399
2427
  completedAt: /* @__PURE__ */ new Date()
2400
2428
  });
2429
+ if (!command) {
2430
+ throw new CommandNotFoundError(id);
2431
+ }
2401
2432
  const device = await database.findDevice(command.deviceId);
2402
2433
  if (device) {
2403
2434
  await emit("command.failed", { device, command, error });
@@ -2877,6 +2908,6 @@ function generateDeviceToken(deviceId, secret, expirationSeconds) {
2877
2908
  return `${header}.${payload}.${signature}`;
2878
2909
  }
2879
2910
 
2880
- export { ApplicationNotFoundError, AuthenticationError, AuthorizationError, DeviceNotFoundError, EnrollmentError, GroupNotFoundError, MDMError, PolicyNotFoundError, RoleNotFoundError, TenantNotFoundError, UserNotFoundError, ValidationError, camelToSnake, createAuditManager, createAuthorizationManager, createDashboardManager, createMDM, createMemoryPluginStorageAdapter, createMessageQueueManager, createPluginKey, createPluginStorageAdapter, createScheduleManager, createTenantManager, createWebhookManager, getColumnNames, getPrimaryKey, getTableNames, mdmSchema, parsePluginKey, snakeToCamel, transformToCamelCase, transformToSnakeCase, verifyWebhookSignature };
2911
+ export { AGENT_PROTOCOL_HEADER, AGENT_PROTOCOL_V2, ApplicationNotFoundError, AuthenticationError, AuthorizationError, CommandNotFoundError, DeviceNotFoundError, EnrollmentError, GroupNotFoundError, MDMError, PolicyNotFoundError, RoleNotFoundError, TenantNotFoundError, UserNotFoundError, ValidationError, agentFail, agentOk, camelToSnake, createAuditManager, createAuthorizationManager, createDashboardManager, createMDM, createMemoryPluginStorageAdapter, createMessageQueueManager, createPluginKey, createPluginStorageAdapter, createScheduleManager, createTenantManager, createWebhookManager, getColumnNames, getPrimaryKey, getTableNames, mdmSchema, parsePluginKey, snakeToCamel, transformToCamelCase, transformToSnakeCase, verifyWebhookSignature, wantsAgentProtocolV2 };
2881
2912
  //# sourceMappingURL=index.js.map
2882
2913
  //# sourceMappingURL=index.js.map