@openmdm/core 0.7.0 → 0.9.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.js CHANGED
@@ -1,4 +1,4 @@
1
- import { randomUUID, createHmac, timingSafeEqual } from 'crypto';
1
+ import { randomUUID, createHmac, createPublicKey, verify, timingSafeEqual } from 'crypto';
2
2
 
3
3
  // src/index.ts
4
4
 
@@ -72,14 +72,69 @@ var ValidationError = class extends MDMError {
72
72
  super(message, "VALIDATION_ERROR", 400, details);
73
73
  }
74
74
  };
75
+
76
+ // src/logger.ts
77
+ function normalize(...args) {
78
+ if (args.length === 1) {
79
+ return { context: void 0, message: args[0] };
80
+ }
81
+ return { context: args[0], message: args[1] };
82
+ }
83
+ function createConsoleLogger(scope = []) {
84
+ const prefix = scope.length > 0 ? `[openmdm:${scope.join(":")}]` : "[openmdm]";
85
+ const render = (context) => {
86
+ if (!context || Object.keys(context).length === 0) return "";
87
+ try {
88
+ return " " + JSON.stringify(context);
89
+ } catch {
90
+ return " " + String(context);
91
+ }
92
+ };
93
+ return {
94
+ debug: (...args) => {
95
+ const { context, message } = normalize(...args);
96
+ if (!process.env.DEBUG) return;
97
+ console.debug(`${prefix} ${message}${render(context)}`);
98
+ },
99
+ info: (...args) => {
100
+ const { context, message } = normalize(...args);
101
+ console.log(`${prefix} ${message}${render(context)}`);
102
+ },
103
+ warn: (...args) => {
104
+ const { context, message } = normalize(...args);
105
+ console.warn(`${prefix} ${message}${render(context)}`);
106
+ },
107
+ error: (...args) => {
108
+ const { context, message } = normalize(...args);
109
+ console.error(`${prefix} ${message}${render(context)}`);
110
+ },
111
+ child: (bindings) => {
112
+ const componentPart = typeof bindings.component === "string" ? [bindings.component] : [];
113
+ return createConsoleLogger([...scope, ...componentPart]);
114
+ }
115
+ };
116
+ }
117
+ function createSilentLogger() {
118
+ const silent = {
119
+ debug: () => void 0,
120
+ info: () => void 0,
121
+ warn: () => void 0,
122
+ error: () => void 0,
123
+ child: () => silent
124
+ };
125
+ return silent;
126
+ }
127
+
128
+ // src/webhooks.ts
75
129
  var DEFAULT_RETRY_CONFIG = {
76
130
  maxRetries: 3,
77
131
  initialDelay: 1e3,
78
132
  maxDelay: 3e4
79
133
  };
80
- function createWebhookManager(config) {
134
+ function createWebhookManager(config, logger = createSilentLogger()) {
81
135
  const endpoints = /* @__PURE__ */ new Map();
82
136
  const retryConfig = { ...DEFAULT_RETRY_CONFIG, ...config.retry };
137
+ const log = logger.child({ component: "webhooks" });
83
138
  if (config.endpoints) {
84
139
  for (const endpoint of config.endpoints) {
85
140
  endpoints.set(endpoint.id, endpoint);
@@ -181,9 +236,14 @@ function createWebhookManager(config) {
181
236
  const results = await Promise.all(deliveryPromises);
182
237
  for (const result of results) {
183
238
  if (!result.success) {
184
- console.error(
185
- `[OpenMDM] Webhook delivery failed to endpoint ${result.endpointId}:`,
186
- result.error
239
+ log.error(
240
+ {
241
+ endpointId: result.endpointId,
242
+ statusCode: result.statusCode,
243
+ retryCount: result.retryCount,
244
+ err: result.error
245
+ },
246
+ "Webhook delivery failed"
187
247
  );
188
248
  }
189
249
  }
@@ -1033,12 +1093,20 @@ function createMessageQueueManager(db) {
1033
1093
  }
1034
1094
 
1035
1095
  // src/dashboard.ts
1096
+ function assertNoTenantScopeRequested(tenantId, method) {
1097
+ if (tenantId) {
1098
+ throw new Error(
1099
+ `DashboardManager.${method} was called with a tenantId but the database adapter does not implement tenant-scoped dashboard queries. Implement the matching DatabaseAdapter method, or omit tenantId to accept global stats. See docs/proposals/tenant-rbac-audit.md for context.`
1100
+ );
1101
+ }
1102
+ }
1036
1103
  function createDashboardManager(db) {
1037
1104
  return {
1038
1105
  async getStats(_tenantId) {
1039
1106
  if (db.getDashboardStats) {
1040
1107
  return db.getDashboardStats(_tenantId);
1041
1108
  }
1109
+ assertNoTenantScopeRequested(_tenantId, "getStats");
1042
1110
  const devices = await db.listDevices({
1043
1111
  limit: 1e4
1044
1112
  // Get all for counting
@@ -1096,6 +1164,7 @@ function createDashboardManager(db) {
1096
1164
  if (db.getDeviceStatusBreakdown) {
1097
1165
  return db.getDeviceStatusBreakdown(_tenantId);
1098
1166
  }
1167
+ assertNoTenantScopeRequested(_tenantId, "getDeviceStatusBreakdown");
1099
1168
  const devices = await db.listDevices({
1100
1169
  limit: 1e4
1101
1170
  });
@@ -1128,6 +1197,7 @@ function createDashboardManager(db) {
1128
1197
  if (db.getEnrollmentTrend) {
1129
1198
  return db.getEnrollmentTrend(days, _tenantId);
1130
1199
  }
1200
+ assertNoTenantScopeRequested(_tenantId, "getEnrollmentTrend");
1131
1201
  const now = /* @__PURE__ */ new Date();
1132
1202
  const startDate = new Date(now.getTime() - days * 24 * 60 * 60 * 1e3);
1133
1203
  const events = await db.listEvents({
@@ -1184,6 +1254,7 @@ function createDashboardManager(db) {
1184
1254
  if (db.getCommandSuccessRates) {
1185
1255
  return db.getCommandSuccessRates(_tenantId);
1186
1256
  }
1257
+ assertNoTenantScopeRequested(_tenantId, "getCommandSuccessRates");
1187
1258
  const now = /* @__PURE__ */ new Date();
1188
1259
  const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1e3);
1189
1260
  const commands = await db.listCommands({ limit: 1e4 });
@@ -1232,6 +1303,7 @@ function createDashboardManager(db) {
1232
1303
  if (db.getAppInstallationSummary) {
1233
1304
  return db.getAppInstallationSummary(_tenantId);
1234
1305
  }
1306
+ assertNoTenantScopeRequested(_tenantId, "getAppInstallationSummary");
1235
1307
  const apps = await db.listApplications();
1236
1308
  const appMap = new Map(apps.map((a) => [a.packageName, a]));
1237
1309
  const byStatus = {
@@ -1277,7 +1349,6 @@ function createPluginStorageAdapter(db) {
1277
1349
  const value = await db.getPluginValue(pluginName, key);
1278
1350
  return value;
1279
1351
  }
1280
- console.warn("Plugin storage not supported by database adapter");
1281
1352
  return null;
1282
1353
  },
1283
1354
  async set(pluginName, key, value) {
@@ -1285,20 +1356,17 @@ function createPluginStorageAdapter(db) {
1285
1356
  await db.setPluginValue(pluginName, key, value);
1286
1357
  return;
1287
1358
  }
1288
- console.warn("Plugin storage not supported by database adapter");
1289
1359
  },
1290
1360
  async delete(pluginName, key) {
1291
1361
  if (db.deletePluginValue) {
1292
1362
  await db.deletePluginValue(pluginName, key);
1293
1363
  return;
1294
1364
  }
1295
- console.warn("Plugin storage not supported by database adapter");
1296
1365
  },
1297
1366
  async list(pluginName, prefix) {
1298
1367
  if (db.listPluginKeys) {
1299
1368
  return db.listPluginKeys(pluginName, prefix);
1300
1369
  }
1301
- console.warn("Plugin storage not supported by database adapter");
1302
1370
  return [];
1303
1371
  },
1304
1372
  async clear(pluginName) {
@@ -1306,7 +1374,6 @@ function createPluginStorageAdapter(db) {
1306
1374
  await db.clearPluginData(pluginName);
1307
1375
  return;
1308
1376
  }
1309
- console.warn("Plugin storage not supported by database adapter");
1310
1377
  }
1311
1378
  };
1312
1379
  }
@@ -1352,6 +1419,135 @@ function parsePluginKey(key) {
1352
1419
  const [namespace, ...parts] = key.split(":");
1353
1420
  return { namespace, parts };
1354
1421
  }
1422
+ function importPublicKeyFromSpki(spkiBase64) {
1423
+ let buffer;
1424
+ try {
1425
+ buffer = Buffer.from(spkiBase64, "base64");
1426
+ } catch (err) {
1427
+ throw new InvalidPublicKeyError(
1428
+ "Public key is not valid base64",
1429
+ err instanceof Error ? err : void 0
1430
+ );
1431
+ }
1432
+ if (buffer.length === 0) {
1433
+ throw new InvalidPublicKeyError("Public key is empty");
1434
+ }
1435
+ try {
1436
+ const key = createPublicKey({
1437
+ key: buffer,
1438
+ format: "der",
1439
+ type: "spki"
1440
+ });
1441
+ const asymmetricKeyType = key.asymmetricKeyType;
1442
+ if (asymmetricKeyType !== "ec") {
1443
+ throw new InvalidPublicKeyError(
1444
+ `Expected EC key, got ${asymmetricKeyType ?? "unknown"}`
1445
+ );
1446
+ }
1447
+ const curve = key.asymmetricKeyDetails?.namedCurve;
1448
+ if (curve && curve !== "prime256v1" && curve !== "P-256") {
1449
+ throw new InvalidPublicKeyError(
1450
+ `Unsupported EC curve: ${curve}. Only P-256 is accepted.`
1451
+ );
1452
+ }
1453
+ return key;
1454
+ } catch (err) {
1455
+ if (err instanceof InvalidPublicKeyError) throw err;
1456
+ throw new InvalidPublicKeyError(
1457
+ "Failed to parse SPKI public key",
1458
+ err instanceof Error ? err : void 0
1459
+ );
1460
+ }
1461
+ }
1462
+ function verifyEcdsaSignature(publicKey, message, signatureBase64) {
1463
+ const key = typeof publicKey === "string" ? importPublicKeyFromSpki(publicKey) : publicKey;
1464
+ let signatureBuffer;
1465
+ try {
1466
+ signatureBuffer = Buffer.from(signatureBase64, "base64");
1467
+ } catch {
1468
+ return false;
1469
+ }
1470
+ if (signatureBuffer.length === 0) return false;
1471
+ try {
1472
+ return verify("sha256", Buffer.from(message, "utf8"), key, signatureBuffer);
1473
+ } catch {
1474
+ return false;
1475
+ }
1476
+ }
1477
+ function canonicalEnrollmentMessage(parts) {
1478
+ return [
1479
+ parts.publicKey,
1480
+ parts.model,
1481
+ parts.manufacturer,
1482
+ parts.osVersion,
1483
+ parts.serialNumber ?? "",
1484
+ parts.imei ?? "",
1485
+ parts.macAddress ?? "",
1486
+ parts.androidId ?? "",
1487
+ parts.method,
1488
+ parts.timestamp,
1489
+ parts.challenge
1490
+ ].join("|");
1491
+ }
1492
+ function canonicalDeviceRequestMessage(parts) {
1493
+ return [parts.deviceId, parts.timestamp, parts.body, parts.nonce ?? ""].join("|");
1494
+ }
1495
+ async function verifyDeviceRequest(opts) {
1496
+ const device = await opts.mdm.devices.get(opts.deviceId);
1497
+ if (!device) {
1498
+ return { ok: false, reason: "not-found" };
1499
+ }
1500
+ if (!device.publicKey) {
1501
+ return { ok: false, reason: "no-pinned-key", device };
1502
+ }
1503
+ let verified;
1504
+ try {
1505
+ verified = verifyEcdsaSignature(
1506
+ device.publicKey,
1507
+ opts.canonicalMessage,
1508
+ opts.signatureBase64
1509
+ );
1510
+ } catch (err) {
1511
+ opts.mdm.logger.child({ component: "device-identity" }).error(
1512
+ {
1513
+ deviceId: opts.deviceId,
1514
+ err: err instanceof Error ? err.message : String(err)
1515
+ },
1516
+ "Pinned public key failed to parse"
1517
+ );
1518
+ return { ok: false, reason: "signature-invalid", device };
1519
+ }
1520
+ if (!verified) {
1521
+ return { ok: false, reason: "signature-invalid", device };
1522
+ }
1523
+ return { ok: true, device };
1524
+ }
1525
+ var InvalidPublicKeyError = class extends Error {
1526
+ constructor(message, cause) {
1527
+ super(message);
1528
+ this.cause = cause;
1529
+ this.name = "InvalidPublicKeyError";
1530
+ }
1531
+ code = "INVALID_PUBLIC_KEY";
1532
+ };
1533
+ var PublicKeyMismatchError = class extends Error {
1534
+ constructor(deviceId) {
1535
+ super(
1536
+ `Device ${deviceId} is already enrolled with a different pinned public key`
1537
+ );
1538
+ this.deviceId = deviceId;
1539
+ this.name = "PublicKeyMismatchError";
1540
+ }
1541
+ code = "PUBLIC_KEY_MISMATCH";
1542
+ };
1543
+ var ChallengeInvalidError = class extends Error {
1544
+ constructor(message, challenge) {
1545
+ super(message);
1546
+ this.challenge = challenge;
1547
+ this.name = "ChallengeInvalidError";
1548
+ }
1549
+ code = "CHALLENGE_INVALID";
1550
+ };
1355
1551
 
1356
1552
  // src/schema.ts
1357
1553
  var mdmSchema = {
@@ -2042,9 +2238,19 @@ function wantsAgentProtocolV2(headerValue) {
2042
2238
  // src/index.ts
2043
2239
  function createMDM(config) {
2044
2240
  const { database, push, enrollment, webhooks: webhooksConfig, plugins = [] } = config;
2241
+ const logger = config.logger ?? createConsoleLogger();
2242
+ const errorMessage = (err) => {
2243
+ if (err instanceof Error) return err.message;
2244
+ if (typeof err === "string") return err;
2245
+ try {
2246
+ return JSON.stringify(err);
2247
+ } catch {
2248
+ return String(err);
2249
+ }
2250
+ };
2045
2251
  const eventHandlers = /* @__PURE__ */ new Map();
2046
- const pushAdapter = push ? createPushAdapter(push, database) : createStubPushAdapter();
2047
- const webhookManager = webhooksConfig ? createWebhookManager(webhooksConfig) : void 0;
2252
+ const pushAdapter = push ? createPushAdapter(push, database, logger) : createStubPushAdapter(logger);
2253
+ const webhookManager = webhooksConfig ? createWebhookManager(webhooksConfig, logger) : void 0;
2048
2254
  const tenantManager = config.multiTenancy?.enabled ? createTenantManager(database) : void 0;
2049
2255
  const authorizationManager = config.authorization?.enabled ? createAuthorizationManager(database) : void 0;
2050
2256
  const auditManager = config.audit?.enabled ? createAuditManager(database) : void 0;
@@ -2078,11 +2284,14 @@ function createMDM(config) {
2078
2284
  payload: eventRecord.payload
2079
2285
  });
2080
2286
  } catch (error) {
2081
- console.error("[OpenMDM] Failed to persist event:", error);
2287
+ logger.error({ err: errorMessage(error), event }, "Failed to persist event");
2082
2288
  }
2083
2289
  if (webhookManager) {
2084
2290
  webhookManager.deliver(eventRecord).catch((error) => {
2085
- console.error("[OpenMDM] Webhook delivery error:", error);
2291
+ logger.error(
2292
+ { err: errorMessage(error), event },
2293
+ "Webhook delivery error"
2294
+ );
2086
2295
  });
2087
2296
  }
2088
2297
  if (handlers) {
@@ -2090,7 +2299,10 @@ function createMDM(config) {
2090
2299
  try {
2091
2300
  await handler(eventRecord);
2092
2301
  } catch (error) {
2093
- console.error(`[OpenMDM] Event handler error for ${event}:`, error);
2302
+ logger.error(
2303
+ { err: errorMessage(error), event },
2304
+ "Event handler threw"
2305
+ );
2094
2306
  }
2095
2307
  }
2096
2308
  }
@@ -2098,7 +2310,7 @@ function createMDM(config) {
2098
2310
  try {
2099
2311
  await config.onEvent(eventRecord);
2100
2312
  } catch (error) {
2101
- console.error("[OpenMDM] onEvent hook error:", error);
2313
+ logger.error({ err: errorMessage(error) }, "onEvent hook threw");
2102
2314
  }
2103
2315
  }
2104
2316
  };
@@ -2579,7 +2791,13 @@ function createMDM(config) {
2579
2791
  `Enrollment method '${request.method}' is not allowed`
2580
2792
  );
2581
2793
  }
2582
- if (enrollment?.deviceSecret) {
2794
+ const isPinnedKeyPath = Boolean(request.publicKey);
2795
+ if (!isPinnedKeyPath && enrollment?.pinnedKey?.required) {
2796
+ throw new EnrollmentError(
2797
+ "Pinned-key enrollment is required but the request carried no publicKey. The agent must generate a Keystore keypair and submit the SPKI public key alongside an ECDSA signature over the canonical enrollment message."
2798
+ );
2799
+ }
2800
+ if (!isPinnedKeyPath && enrollment?.deviceSecret) {
2583
2801
  const isValid = verifyEnrollmentSignature(
2584
2802
  request,
2585
2803
  enrollment.deviceSecret
@@ -2588,6 +2806,65 @@ function createMDM(config) {
2588
2806
  throw new EnrollmentError("Invalid enrollment signature");
2589
2807
  }
2590
2808
  }
2809
+ let challengeRecord = null;
2810
+ let importedPublicKey = null;
2811
+ if (isPinnedKeyPath) {
2812
+ if (!request.attestationChallenge) {
2813
+ throw new EnrollmentError(
2814
+ "Pinned-key enrollment requires attestationChallenge. Fetch a fresh challenge from /agent/enroll/challenge first."
2815
+ );
2816
+ }
2817
+ if (!database.consumeEnrollmentChallenge) {
2818
+ throw new EnrollmentError(
2819
+ "Pinned-key enrollment requires an adapter that implements enrollment challenge storage. Upgrade to a database adapter that supports it, or submit an HMAC-signed enrollment instead."
2820
+ );
2821
+ }
2822
+ try {
2823
+ importedPublicKey = importPublicKeyFromSpki(request.publicKey);
2824
+ } catch (err) {
2825
+ throw new EnrollmentError(
2826
+ err instanceof Error ? `Invalid enrollment public key: ${err.message}` : "Invalid enrollment public key"
2827
+ );
2828
+ }
2829
+ challengeRecord = await database.consumeEnrollmentChallenge(
2830
+ request.attestationChallenge
2831
+ );
2832
+ if (!challengeRecord) {
2833
+ throw new ChallengeInvalidError(
2834
+ "Enrollment challenge is missing, expired, or already consumed",
2835
+ request.attestationChallenge
2836
+ );
2837
+ }
2838
+ if (challengeRecord.expiresAt.getTime() < Date.now()) {
2839
+ throw new ChallengeInvalidError(
2840
+ "Enrollment challenge has expired",
2841
+ request.attestationChallenge
2842
+ );
2843
+ }
2844
+ const canonical = canonicalEnrollmentMessage({
2845
+ publicKey: request.publicKey,
2846
+ model: request.model,
2847
+ manufacturer: request.manufacturer,
2848
+ osVersion: request.osVersion,
2849
+ serialNumber: request.serialNumber,
2850
+ imei: request.imei,
2851
+ macAddress: request.macAddress,
2852
+ androidId: request.androidId,
2853
+ method: request.method,
2854
+ timestamp: request.timestamp,
2855
+ challenge: request.attestationChallenge
2856
+ });
2857
+ const verified = verifyEcdsaSignature(
2858
+ importedPublicKey,
2859
+ canonical,
2860
+ request.signature
2861
+ );
2862
+ if (!verified) {
2863
+ throw new EnrollmentError(
2864
+ "Invalid enrollment signature (device-pinned-key path)"
2865
+ );
2866
+ }
2867
+ }
2591
2868
  if (enrollment?.validate) {
2592
2869
  const isValid = await enrollment.validate(request);
2593
2870
  if (!isValid) {
@@ -2602,13 +2879,23 @@ function createMDM(config) {
2602
2879
  }
2603
2880
  let device = await database.findDeviceByEnrollmentId(enrollmentId);
2604
2881
  if (device) {
2605
- device = await database.updateDevice(device.id, {
2882
+ if (isPinnedKeyPath && device.publicKey) {
2883
+ if (device.publicKey !== request.publicKey) {
2884
+ throw new PublicKeyMismatchError(device.id);
2885
+ }
2886
+ }
2887
+ const updateInput = {
2606
2888
  status: "enrolled",
2607
2889
  model: request.model,
2608
2890
  manufacturer: request.manufacturer,
2609
2891
  osVersion: request.osVersion,
2610
2892
  lastSync: /* @__PURE__ */ new Date()
2611
- });
2893
+ };
2894
+ if (isPinnedKeyPath && !device.publicKey) {
2895
+ updateInput.publicKey = request.publicKey;
2896
+ updateInput.enrollmentMethod = "pinned-key";
2897
+ }
2898
+ device = await database.updateDevice(device.id, updateInput);
2612
2899
  } else if (enrollment?.autoEnroll) {
2613
2900
  device = await database.createDevice({
2614
2901
  enrollmentId,
@@ -2621,6 +2908,12 @@ function createMDM(config) {
2621
2908
  androidId: request.androidId,
2622
2909
  policyId: request.policyId || enrollment.defaultPolicyId
2623
2910
  });
2911
+ if (isPinnedKeyPath) {
2912
+ device = await database.updateDevice(device.id, {
2913
+ publicKey: request.publicKey,
2914
+ enrollmentMethod: "pinned-key"
2915
+ });
2916
+ }
2624
2917
  if (enrollment.defaultGroupId) {
2625
2918
  await database.addDeviceToGroup(device.id, enrollment.defaultGroupId);
2626
2919
  }
@@ -2635,6 +2928,12 @@ function createMDM(config) {
2635
2928
  macAddress: request.macAddress,
2636
2929
  androidId: request.androidId
2637
2930
  });
2931
+ if (isPinnedKeyPath) {
2932
+ device = await database.updateDevice(device.id, {
2933
+ publicKey: request.publicKey,
2934
+ enrollmentMethod: "pinned-key"
2935
+ });
2936
+ }
2638
2937
  } else {
2639
2938
  throw new EnrollmentError(
2640
2939
  "Device not registered and auto-enroll is disabled"
@@ -2775,6 +3074,7 @@ function createMDM(config) {
2775
3074
  push: pushAdapter,
2776
3075
  webhooks: webhookManager,
2777
3076
  db: database,
3077
+ logger,
2778
3078
  config,
2779
3079
  on,
2780
3080
  emit,
@@ -2797,11 +3097,11 @@ function createMDM(config) {
2797
3097
  if (plugin.onInit) {
2798
3098
  try {
2799
3099
  await plugin.onInit(instance);
2800
- console.log(`[OpenMDM] Plugin initialized: ${plugin.name}`);
3100
+ logger.info({ plugin: plugin.name }, "Plugin initialized");
2801
3101
  } catch (error) {
2802
- console.error(
2803
- `[OpenMDM] Failed to initialize plugin ${plugin.name}:`,
2804
- error
3102
+ logger.error(
3103
+ { plugin: plugin.name, err: errorMessage(error) },
3104
+ "Failed to initialize plugin"
2805
3105
  );
2806
3106
  }
2807
3107
  }
@@ -2809,21 +3109,23 @@ function createMDM(config) {
2809
3109
  })();
2810
3110
  return instance;
2811
3111
  }
2812
- function createPushAdapter(config, database) {
3112
+ function createPushAdapter(config, database, logger) {
2813
3113
  if (!config) {
2814
- return createStubPushAdapter();
3114
+ return createStubPushAdapter(logger);
2815
3115
  }
3116
+ const pushLogger = logger.child({ component: "push" });
2816
3117
  return {
2817
3118
  async send(deviceId, message) {
2818
- console.log(
2819
- `[OpenMDM] Push to ${deviceId}: ${message.type}`,
2820
- message.payload
3119
+ pushLogger.debug(
3120
+ { deviceId, type: message.type, payload: message.payload },
3121
+ "send"
2821
3122
  );
2822
3123
  return { success: true, messageId: randomUUID() };
2823
3124
  },
2824
3125
  async sendBatch(deviceIds, message) {
2825
- console.log(
2826
- `[OpenMDM] Push to ${deviceIds.length} devices: ${message.type}`
3126
+ pushLogger.debug(
3127
+ { count: deviceIds.length, type: message.type },
3128
+ "sendBatch"
2827
3129
  );
2828
3130
  const results = deviceIds.map((deviceId) => ({
2829
3131
  deviceId,
@@ -2853,15 +3155,17 @@ function createPushAdapter(config, database) {
2853
3155
  }
2854
3156
  };
2855
3157
  }
2856
- function createStubPushAdapter() {
3158
+ function createStubPushAdapter(logger) {
3159
+ const stubLogger = logger.child({ component: "push-stub" });
2857
3160
  return {
2858
3161
  async send(deviceId, message) {
2859
- console.log(`[OpenMDM] Push (stub): ${deviceId} <- ${message.type}`);
3162
+ stubLogger.debug({ deviceId, type: message.type }, "send (stub)");
2860
3163
  return { success: true, messageId: "stub" };
2861
3164
  },
2862
3165
  async sendBatch(deviceIds, message) {
2863
- console.log(
2864
- `[OpenMDM] Push (stub): ${deviceIds.length} devices <- ${message.type}`
3166
+ stubLogger.debug(
3167
+ { count: deviceIds.length, type: message.type },
3168
+ "sendBatch (stub)"
2865
3169
  );
2866
3170
  return {
2867
3171
  successCount: deviceIds.length,
@@ -2917,6 +3221,6 @@ function generateDeviceToken(deviceId, secret, expirationSeconds) {
2917
3221
  return `${header}.${payload}.${signature}`;
2918
3222
  }
2919
3223
 
2920
- 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, verifyEnrollmentSignature, verifyWebhookSignature, wantsAgentProtocolV2 };
3224
+ export { AGENT_PROTOCOL_HEADER, AGENT_PROTOCOL_V2, ApplicationNotFoundError, AuthenticationError, AuthorizationError, ChallengeInvalidError, CommandNotFoundError, DeviceNotFoundError, EnrollmentError, GroupNotFoundError, InvalidPublicKeyError, MDMError, PolicyNotFoundError, PublicKeyMismatchError, RoleNotFoundError, TenantNotFoundError, UserNotFoundError, ValidationError, agentFail, agentOk, camelToSnake, canonicalDeviceRequestMessage, canonicalEnrollmentMessage, createAuditManager, createAuthorizationManager, createConsoleLogger, createDashboardManager, createMDM, createMemoryPluginStorageAdapter, createMessageQueueManager, createPluginKey, createPluginStorageAdapter, createScheduleManager, createSilentLogger, createTenantManager, createWebhookManager, getColumnNames, getPrimaryKey, getTableNames, importPublicKeyFromSpki, mdmSchema, parsePluginKey, snakeToCamel, transformToCamelCase, transformToSnakeCase, verifyDeviceRequest, verifyEcdsaSignature, verifyEnrollmentSignature, verifyWebhookSignature, wantsAgentProtocolV2 };
2921
3225
  //# sourceMappingURL=index.js.map
2922
3226
  //# sourceMappingURL=index.js.map