@openmdm/hono 0.2.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/LICENSE +21 -0
- package/dist/index.d.ts +67 -0
- package/dist/index.js +462 -0
- package/dist/index.js.map +1 -0
- package/package.json +58 -0
- package/src/index.ts +746 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,746 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenMDM Hono Adapter
|
|
3
|
+
*
|
|
4
|
+
* HTTP routes adapter for Hono framework.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { Hono } from 'hono';
|
|
9
|
+
* import { createMDM } from '@openmdm/core';
|
|
10
|
+
* import { honoAdapter } from '@openmdm/hono';
|
|
11
|
+
*
|
|
12
|
+
* const mdm = createMDM({ ... });
|
|
13
|
+
* const app = new Hono<MDMEnv>();
|
|
14
|
+
*
|
|
15
|
+
* // Mount MDM routes
|
|
16
|
+
* app.route('/mdm', honoAdapter(mdm));
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { Hono } from 'hono';
|
|
21
|
+
import { HTTPException } from 'hono/http-exception';
|
|
22
|
+
import type { Context, MiddlewareHandler, Env } from 'hono';
|
|
23
|
+
import type {
|
|
24
|
+
MDMInstance,
|
|
25
|
+
EnrollmentRequest,
|
|
26
|
+
Heartbeat,
|
|
27
|
+
DeviceFilter,
|
|
28
|
+
CommandFilter,
|
|
29
|
+
CreatePolicyInput,
|
|
30
|
+
UpdatePolicyInput,
|
|
31
|
+
CreateApplicationInput,
|
|
32
|
+
UpdateApplicationInput,
|
|
33
|
+
CreateGroupInput,
|
|
34
|
+
UpdateGroupInput,
|
|
35
|
+
SendCommandInput,
|
|
36
|
+
MDMError,
|
|
37
|
+
AuthenticationError,
|
|
38
|
+
AuthorizationError,
|
|
39
|
+
} from '@openmdm/core';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Context variables set by OpenMDM middlewares
|
|
43
|
+
*/
|
|
44
|
+
interface MDMVariables {
|
|
45
|
+
deviceId?: string;
|
|
46
|
+
user?: unknown;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Hono environment type for OpenMDM routes
|
|
51
|
+
*/
|
|
52
|
+
type MDMEnv = {
|
|
53
|
+
Variables: MDMVariables;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export interface HonoAdapterOptions {
|
|
57
|
+
/**
|
|
58
|
+
* Base path prefix for all routes (default: '')
|
|
59
|
+
*/
|
|
60
|
+
basePath?: string;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Enable authentication middleware for admin routes
|
|
64
|
+
*/
|
|
65
|
+
enableAuth?: boolean;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Custom error handler
|
|
69
|
+
*/
|
|
70
|
+
onError?: (error: Error, c: Context) => Response | Promise<Response>;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Routes to expose (default: all)
|
|
74
|
+
*/
|
|
75
|
+
routes?: {
|
|
76
|
+
enrollment?: boolean;
|
|
77
|
+
devices?: boolean;
|
|
78
|
+
policies?: boolean;
|
|
79
|
+
applications?: boolean;
|
|
80
|
+
groups?: boolean;
|
|
81
|
+
commands?: boolean;
|
|
82
|
+
events?: boolean;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Create a Hono router with OpenMDM API routes
|
|
88
|
+
*/
|
|
89
|
+
export function honoAdapter(
|
|
90
|
+
mdm: MDMInstance,
|
|
91
|
+
options: HonoAdapterOptions = {}
|
|
92
|
+
): Hono<MDMEnv> {
|
|
93
|
+
const app = new Hono<MDMEnv>();
|
|
94
|
+
|
|
95
|
+
const routes = {
|
|
96
|
+
enrollment: true,
|
|
97
|
+
devices: true,
|
|
98
|
+
policies: true,
|
|
99
|
+
applications: true,
|
|
100
|
+
groups: true,
|
|
101
|
+
commands: true,
|
|
102
|
+
events: true,
|
|
103
|
+
...options.routes,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// Error handling middleware
|
|
107
|
+
app.onError((error, c) => {
|
|
108
|
+
if (options.onError) {
|
|
109
|
+
return options.onError(error, c);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.error('[OpenMDM] Error:', error);
|
|
113
|
+
|
|
114
|
+
if (error instanceof HTTPException) {
|
|
115
|
+
return c.json({ error: error.message }, error.status);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const mdmError = error as MDMError;
|
|
119
|
+
if (mdmError.code && mdmError.statusCode) {
|
|
120
|
+
return c.json(
|
|
121
|
+
{
|
|
122
|
+
error: mdmError.message,
|
|
123
|
+
code: mdmError.code,
|
|
124
|
+
details: mdmError.details,
|
|
125
|
+
},
|
|
126
|
+
mdmError.statusCode as any
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return c.json({ error: 'Internal server error' }, 500);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Device authentication middleware
|
|
134
|
+
const deviceAuth: MiddlewareHandler = async (c, next) => {
|
|
135
|
+
const token = c.req.header('Authorization')?.replace('Bearer ', '');
|
|
136
|
+
const deviceId = c.req.header('X-Device-Id');
|
|
137
|
+
|
|
138
|
+
if (!token && !deviceId) {
|
|
139
|
+
throw new HTTPException(401, { message: 'Device authentication required' });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (token) {
|
|
143
|
+
const result = await mdm.verifyDeviceToken(token);
|
|
144
|
+
if (!result) {
|
|
145
|
+
throw new HTTPException(401, { message: 'Invalid device token' });
|
|
146
|
+
}
|
|
147
|
+
c.set('deviceId', result.deviceId);
|
|
148
|
+
} else if (deviceId) {
|
|
149
|
+
c.set('deviceId', deviceId);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
await next();
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// Admin authentication middleware
|
|
156
|
+
const adminAuth: MiddlewareHandler = async (c, next) => {
|
|
157
|
+
if (!mdm.config.auth) {
|
|
158
|
+
await next();
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const user = await mdm.config.auth.getUser(c);
|
|
163
|
+
if (!user) {
|
|
164
|
+
throw new HTTPException(401, { message: 'Authentication required' });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (mdm.config.auth.isAdmin) {
|
|
168
|
+
const isAdmin = await mdm.config.auth.isAdmin(user);
|
|
169
|
+
if (!isAdmin) {
|
|
170
|
+
throw new HTTPException(403, { message: 'Admin access required' });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
c.set('user', user);
|
|
175
|
+
await next();
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// ============================================
|
|
179
|
+
// Enrollment Routes (Device-facing)
|
|
180
|
+
// ============================================
|
|
181
|
+
|
|
182
|
+
if (routes.enrollment) {
|
|
183
|
+
const enrollment = new Hono<MDMEnv>();
|
|
184
|
+
|
|
185
|
+
// Enroll device
|
|
186
|
+
enrollment.post('/enroll', async (c) => {
|
|
187
|
+
const body = await c.req.json<EnrollmentRequest>();
|
|
188
|
+
|
|
189
|
+
// Validate required fields
|
|
190
|
+
if (!body.model || !body.manufacturer || !body.osVersion) {
|
|
191
|
+
throw new HTTPException(400, {
|
|
192
|
+
message: 'Missing required fields: model, manufacturer, osVersion',
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (!body.macAddress && !body.serialNumber && !body.imei && !body.androidId) {
|
|
197
|
+
throw new HTTPException(400, {
|
|
198
|
+
message: 'At least one device identifier required',
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const result = await mdm.enroll(body);
|
|
203
|
+
|
|
204
|
+
// Add server URL from request if not configured
|
|
205
|
+
if (!result.serverUrl) {
|
|
206
|
+
const url = new URL(c.req.url);
|
|
207
|
+
result.serverUrl = `${url.protocol}//${url.host}`;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return c.json(result, 201);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Device heartbeat
|
|
214
|
+
enrollment.post('/heartbeat', deviceAuth, async (c) => {
|
|
215
|
+
const deviceId = c.get('deviceId') as string;
|
|
216
|
+
const body = await c.req.json<Omit<Heartbeat, 'deviceId'>>();
|
|
217
|
+
|
|
218
|
+
await mdm.processHeartbeat(deviceId, {
|
|
219
|
+
...body,
|
|
220
|
+
deviceId,
|
|
221
|
+
timestamp: new Date(body.timestamp || Date.now()),
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// Return pending commands for the device
|
|
225
|
+
const pendingCommands = await mdm.commands.getPending(deviceId);
|
|
226
|
+
|
|
227
|
+
return c.json({
|
|
228
|
+
status: 'ok',
|
|
229
|
+
commands: pendingCommands,
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// Get device config/policy
|
|
234
|
+
enrollment.get('/config', deviceAuth, async (c) => {
|
|
235
|
+
const deviceId = c.get('deviceId') as string;
|
|
236
|
+
const device = await mdm.devices.get(deviceId);
|
|
237
|
+
|
|
238
|
+
if (!device) {
|
|
239
|
+
throw new HTTPException(404, { message: 'Device not found' });
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
let policy = null;
|
|
243
|
+
if (device.policyId) {
|
|
244
|
+
policy = await mdm.policies.get(device.policyId);
|
|
245
|
+
} else {
|
|
246
|
+
policy = await mdm.policies.getDefault();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return c.json({
|
|
250
|
+
device: {
|
|
251
|
+
id: device.id,
|
|
252
|
+
enrollmentId: device.enrollmentId,
|
|
253
|
+
status: device.status,
|
|
254
|
+
},
|
|
255
|
+
policy,
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Register push token
|
|
260
|
+
enrollment.post('/push-token', deviceAuth, async (c) => {
|
|
261
|
+
const deviceId = c.get('deviceId') as string;
|
|
262
|
+
const body = await c.req.json<{ provider: string; token: string }>();
|
|
263
|
+
|
|
264
|
+
await mdm.db.upsertPushToken({
|
|
265
|
+
deviceId,
|
|
266
|
+
provider: body.provider as any,
|
|
267
|
+
token: body.token,
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
return c.json({ status: 'ok' });
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// Acknowledge command
|
|
274
|
+
enrollment.post('/commands/:id/ack', deviceAuth, async (c) => {
|
|
275
|
+
const commandId = c.req.param('id');
|
|
276
|
+
const command = await mdm.commands.acknowledge(commandId);
|
|
277
|
+
return c.json(command);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// Complete command
|
|
281
|
+
enrollment.post('/commands/:id/complete', deviceAuth, async (c) => {
|
|
282
|
+
const commandId = c.req.param('id');
|
|
283
|
+
const body = await c.req.json<{ success: boolean; message?: string; data?: unknown }>();
|
|
284
|
+
const command = await mdm.commands.complete(commandId, body);
|
|
285
|
+
return c.json(command);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// Fail command
|
|
289
|
+
enrollment.post('/commands/:id/fail', deviceAuth, async (c) => {
|
|
290
|
+
const commandId = c.req.param('id');
|
|
291
|
+
const body = await c.req.json<{ error: string }>();
|
|
292
|
+
const command = await mdm.commands.fail(commandId, body.error);
|
|
293
|
+
return c.json(command);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
app.route('/agent', enrollment);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// ============================================
|
|
300
|
+
// Device Routes (Admin-facing)
|
|
301
|
+
// ============================================
|
|
302
|
+
|
|
303
|
+
if (routes.devices) {
|
|
304
|
+
const devices = new Hono<MDMEnv>();
|
|
305
|
+
|
|
306
|
+
if (options.enableAuth) {
|
|
307
|
+
devices.use('/*', adminAuth);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// List devices
|
|
311
|
+
devices.get('/', async (c) => {
|
|
312
|
+
const filter: DeviceFilter = {
|
|
313
|
+
status: c.req.query('status') as any,
|
|
314
|
+
policyId: c.req.query('policyId'),
|
|
315
|
+
groupId: c.req.query('groupId'),
|
|
316
|
+
search: c.req.query('search'),
|
|
317
|
+
limit: c.req.query('limit') ? parseInt(c.req.query('limit')!) : undefined,
|
|
318
|
+
offset: c.req.query('offset') ? parseInt(c.req.query('offset')!) : undefined,
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const result = await mdm.devices.list(filter);
|
|
322
|
+
return c.json(result);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// Get device
|
|
326
|
+
devices.get('/:id', async (c) => {
|
|
327
|
+
const device = await mdm.devices.get(c.req.param('id'));
|
|
328
|
+
if (!device) {
|
|
329
|
+
throw new HTTPException(404, { message: 'Device not found' });
|
|
330
|
+
}
|
|
331
|
+
return c.json(device);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// Update device
|
|
335
|
+
devices.patch('/:id', async (c) => {
|
|
336
|
+
const body = await c.req.json();
|
|
337
|
+
const device = await mdm.devices.update(c.req.param('id'), body);
|
|
338
|
+
return c.json(device);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// Delete device
|
|
342
|
+
devices.delete('/:id', async (c) => {
|
|
343
|
+
await mdm.devices.delete(c.req.param('id'));
|
|
344
|
+
return c.json({ status: 'ok' });
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
// Assign policy to device
|
|
348
|
+
devices.post('/:id/policy', async (c) => {
|
|
349
|
+
const { policyId } = await c.req.json<{ policyId: string | null }>();
|
|
350
|
+
const device = await mdm.devices.assignPolicy(c.req.param('id'), policyId);
|
|
351
|
+
return c.json(device);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
// Get device groups
|
|
355
|
+
devices.get('/:id/groups', async (c) => {
|
|
356
|
+
const groups = await mdm.devices.getGroups(c.req.param('id'));
|
|
357
|
+
return c.json({ groups });
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// Add device to group
|
|
361
|
+
devices.post('/:id/groups', async (c) => {
|
|
362
|
+
const { groupId } = await c.req.json<{ groupId: string }>();
|
|
363
|
+
await mdm.devices.addToGroup(c.req.param('id'), groupId);
|
|
364
|
+
return c.json({ status: 'ok' });
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
// Remove device from group
|
|
368
|
+
devices.delete('/:id/groups/:groupId', async (c) => {
|
|
369
|
+
await mdm.devices.removeFromGroup(c.req.param('id'), c.req.param('groupId'));
|
|
370
|
+
return c.json({ status: 'ok' });
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// Send command to device
|
|
374
|
+
devices.post('/:id/commands', async (c) => {
|
|
375
|
+
const body = await c.req.json<Omit<SendCommandInput, 'deviceId'>>();
|
|
376
|
+
const command = await mdm.devices.sendCommand(c.req.param('id'), body);
|
|
377
|
+
return c.json(command, 201);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// Convenience: Sync device
|
|
381
|
+
devices.post('/:id/sync', async (c) => {
|
|
382
|
+
const command = await mdm.devices.sync(c.req.param('id'));
|
|
383
|
+
return c.json(command, 201);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
// Convenience: Reboot device
|
|
387
|
+
devices.post('/:id/reboot', async (c) => {
|
|
388
|
+
const command = await mdm.devices.reboot(c.req.param('id'));
|
|
389
|
+
return c.json(command, 201);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// Convenience: Lock device
|
|
393
|
+
devices.post('/:id/lock', async (c) => {
|
|
394
|
+
const body = await c.req.json<{ message?: string }>().catch(() => ({ message: undefined }));
|
|
395
|
+
const command = await mdm.devices.lock(c.req.param('id'), body.message);
|
|
396
|
+
return c.json(command, 201);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
// Convenience: Wipe device
|
|
400
|
+
devices.post('/:id/wipe', async (c) => {
|
|
401
|
+
const body = await c.req.json<{ preserveData?: boolean }>().catch(() => ({ preserveData: undefined }));
|
|
402
|
+
const command = await mdm.devices.wipe(c.req.param('id'), body.preserveData);
|
|
403
|
+
return c.json(command, 201);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
app.route('/devices', devices);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// ============================================
|
|
410
|
+
// Policy Routes
|
|
411
|
+
// ============================================
|
|
412
|
+
|
|
413
|
+
if (routes.policies) {
|
|
414
|
+
const policies = new Hono<MDMEnv>();
|
|
415
|
+
|
|
416
|
+
if (options.enableAuth) {
|
|
417
|
+
policies.use('/*', adminAuth);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// List policies
|
|
421
|
+
policies.get('/', async (c) => {
|
|
422
|
+
const result = await mdm.policies.list();
|
|
423
|
+
return c.json({ policies: result });
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
// Get default policy
|
|
427
|
+
policies.get('/default', async (c) => {
|
|
428
|
+
const policy = await mdm.policies.getDefault();
|
|
429
|
+
if (!policy) {
|
|
430
|
+
throw new HTTPException(404, { message: 'No default policy set' });
|
|
431
|
+
}
|
|
432
|
+
return c.json(policy);
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
// Get policy
|
|
436
|
+
policies.get('/:id', async (c) => {
|
|
437
|
+
const policy = await mdm.policies.get(c.req.param('id'));
|
|
438
|
+
if (!policy) {
|
|
439
|
+
throw new HTTPException(404, { message: 'Policy not found' });
|
|
440
|
+
}
|
|
441
|
+
return c.json(policy);
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
// Create policy
|
|
445
|
+
policies.post('/', async (c) => {
|
|
446
|
+
const body = await c.req.json<CreatePolicyInput>();
|
|
447
|
+
const policy = await mdm.policies.create(body);
|
|
448
|
+
return c.json(policy, 201);
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
// Update policy
|
|
452
|
+
policies.patch('/:id', async (c) => {
|
|
453
|
+
const body = await c.req.json<UpdatePolicyInput>();
|
|
454
|
+
const policy = await mdm.policies.update(c.req.param('id'), body);
|
|
455
|
+
return c.json(policy);
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
// Delete policy
|
|
459
|
+
policies.delete('/:id', async (c) => {
|
|
460
|
+
await mdm.policies.delete(c.req.param('id'));
|
|
461
|
+
return c.json({ status: 'ok' });
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// Set default policy
|
|
465
|
+
policies.post('/:id/default', async (c) => {
|
|
466
|
+
const policy = await mdm.policies.setDefault(c.req.param('id'));
|
|
467
|
+
return c.json(policy);
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
// Get devices with this policy
|
|
471
|
+
policies.get('/:id/devices', async (c) => {
|
|
472
|
+
const devices = await mdm.policies.getDevices(c.req.param('id'));
|
|
473
|
+
return c.json({ devices });
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
app.route('/policies', policies);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// ============================================
|
|
480
|
+
// Application Routes
|
|
481
|
+
// ============================================
|
|
482
|
+
|
|
483
|
+
if (routes.applications) {
|
|
484
|
+
const applications = new Hono<MDMEnv>();
|
|
485
|
+
|
|
486
|
+
if (options.enableAuth) {
|
|
487
|
+
applications.use('/*', adminAuth);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// List applications
|
|
491
|
+
applications.get('/', async (c) => {
|
|
492
|
+
const activeOnly = c.req.query('active') === 'true';
|
|
493
|
+
const result = await mdm.apps.list(activeOnly);
|
|
494
|
+
return c.json({ applications: result });
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
// Get application by ID
|
|
498
|
+
applications.get('/:id', async (c) => {
|
|
499
|
+
const app = await mdm.apps.get(c.req.param('id'));
|
|
500
|
+
if (!app) {
|
|
501
|
+
throw new HTTPException(404, { message: 'Application not found' });
|
|
502
|
+
}
|
|
503
|
+
return c.json(app);
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
// Get application by package name
|
|
507
|
+
applications.get('/package/:packageName', async (c) => {
|
|
508
|
+
const version = c.req.query('version');
|
|
509
|
+
const app = await mdm.apps.getByPackage(c.req.param('packageName'), version);
|
|
510
|
+
if (!app) {
|
|
511
|
+
throw new HTTPException(404, { message: 'Application not found' });
|
|
512
|
+
}
|
|
513
|
+
return c.json(app);
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
// Register application
|
|
517
|
+
applications.post('/', async (c) => {
|
|
518
|
+
const body = await c.req.json<CreateApplicationInput>();
|
|
519
|
+
const app = await mdm.apps.register(body);
|
|
520
|
+
return c.json(app, 201);
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
// Update application
|
|
524
|
+
applications.patch('/:id', async (c) => {
|
|
525
|
+
const body = await c.req.json<UpdateApplicationInput>();
|
|
526
|
+
const app = await mdm.apps.update(c.req.param('id'), body);
|
|
527
|
+
return c.json(app);
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
// Delete application
|
|
531
|
+
applications.delete('/:id', async (c) => {
|
|
532
|
+
await mdm.apps.delete(c.req.param('id'));
|
|
533
|
+
return c.json({ status: 'ok' });
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
// Activate application
|
|
537
|
+
applications.post('/:id/activate', async (c) => {
|
|
538
|
+
const app = await mdm.apps.activate(c.req.param('id'));
|
|
539
|
+
return c.json(app);
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
// Deactivate application
|
|
543
|
+
applications.post('/:id/deactivate', async (c) => {
|
|
544
|
+
const app = await mdm.apps.deactivate(c.req.param('id'));
|
|
545
|
+
return c.json(app);
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
// Deploy application
|
|
549
|
+
applications.post('/:packageName/deploy', async (c) => {
|
|
550
|
+
const body = await c.req.json<{
|
|
551
|
+
devices?: string[];
|
|
552
|
+
policies?: string[];
|
|
553
|
+
groups?: string[];
|
|
554
|
+
}>();
|
|
555
|
+
await mdm.apps.deploy(c.req.param('packageName'), body);
|
|
556
|
+
return c.json({ status: 'ok', message: 'Deployment initiated' });
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
// Install app on device
|
|
560
|
+
applications.post('/:packageName/install/:deviceId', async (c) => {
|
|
561
|
+
const version = c.req.query('version');
|
|
562
|
+
const command = await mdm.apps.installOnDevice(
|
|
563
|
+
c.req.param('packageName'),
|
|
564
|
+
c.req.param('deviceId'),
|
|
565
|
+
version
|
|
566
|
+
);
|
|
567
|
+
return c.json(command, 201);
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
// Uninstall app from device
|
|
571
|
+
applications.post('/:packageName/uninstall/:deviceId', async (c) => {
|
|
572
|
+
const command = await mdm.apps.uninstallFromDevice(
|
|
573
|
+
c.req.param('packageName'),
|
|
574
|
+
c.req.param('deviceId')
|
|
575
|
+
);
|
|
576
|
+
return c.json(command, 201);
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
app.route('/applications', applications);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// ============================================
|
|
583
|
+
// Group Routes
|
|
584
|
+
// ============================================
|
|
585
|
+
|
|
586
|
+
if (routes.groups) {
|
|
587
|
+
const groups = new Hono<MDMEnv>();
|
|
588
|
+
|
|
589
|
+
if (options.enableAuth) {
|
|
590
|
+
groups.use('/*', adminAuth);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// List groups
|
|
594
|
+
groups.get('/', async (c) => {
|
|
595
|
+
const result = await mdm.groups.list();
|
|
596
|
+
return c.json({ groups: result });
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
// Get group
|
|
600
|
+
groups.get('/:id', async (c) => {
|
|
601
|
+
const group = await mdm.groups.get(c.req.param('id'));
|
|
602
|
+
if (!group) {
|
|
603
|
+
throw new HTTPException(404, { message: 'Group not found' });
|
|
604
|
+
}
|
|
605
|
+
return c.json(group);
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
// Create group
|
|
609
|
+
groups.post('/', async (c) => {
|
|
610
|
+
const body = await c.req.json<CreateGroupInput>();
|
|
611
|
+
const group = await mdm.groups.create(body);
|
|
612
|
+
return c.json(group, 201);
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
// Update group
|
|
616
|
+
groups.patch('/:id', async (c) => {
|
|
617
|
+
const body = await c.req.json<UpdateGroupInput>();
|
|
618
|
+
const group = await mdm.groups.update(c.req.param('id'), body);
|
|
619
|
+
return c.json(group);
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
// Delete group
|
|
623
|
+
groups.delete('/:id', async (c) => {
|
|
624
|
+
await mdm.groups.delete(c.req.param('id'));
|
|
625
|
+
return c.json({ status: 'ok' });
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
// Get devices in group
|
|
629
|
+
groups.get('/:id/devices', async (c) => {
|
|
630
|
+
const devices = await mdm.groups.getDevices(c.req.param('id'));
|
|
631
|
+
return c.json({ devices });
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
// Add device to group
|
|
635
|
+
groups.post('/:id/devices', async (c) => {
|
|
636
|
+
const { deviceId } = await c.req.json<{ deviceId: string }>();
|
|
637
|
+
await mdm.groups.addDevice(c.req.param('id'), deviceId);
|
|
638
|
+
return c.json({ status: 'ok' });
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
// Remove device from group
|
|
642
|
+
groups.delete('/:id/devices/:deviceId', async (c) => {
|
|
643
|
+
await mdm.groups.removeDevice(c.req.param('id'), c.req.param('deviceId'));
|
|
644
|
+
return c.json({ status: 'ok' });
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
// Get child groups
|
|
648
|
+
groups.get('/:id/children', async (c) => {
|
|
649
|
+
const children = await mdm.groups.getChildren(c.req.param('id'));
|
|
650
|
+
return c.json({ groups: children });
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
app.route('/groups', groups);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// ============================================
|
|
657
|
+
// Command Routes
|
|
658
|
+
// ============================================
|
|
659
|
+
|
|
660
|
+
if (routes.commands) {
|
|
661
|
+
const commands = new Hono<MDMEnv>();
|
|
662
|
+
|
|
663
|
+
if (options.enableAuth) {
|
|
664
|
+
commands.use('/*', adminAuth);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// List commands
|
|
668
|
+
commands.get('/', async (c) => {
|
|
669
|
+
const filter: CommandFilter = {
|
|
670
|
+
deviceId: c.req.query('deviceId'),
|
|
671
|
+
status: c.req.query('status') as any,
|
|
672
|
+
type: c.req.query('type') as any,
|
|
673
|
+
limit: c.req.query('limit') ? parseInt(c.req.query('limit')!) : undefined,
|
|
674
|
+
offset: c.req.query('offset') ? parseInt(c.req.query('offset')!) : undefined,
|
|
675
|
+
};
|
|
676
|
+
|
|
677
|
+
const result = await mdm.commands.list(filter);
|
|
678
|
+
return c.json({ commands: result });
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
// Get command
|
|
682
|
+
commands.get('/:id', async (c) => {
|
|
683
|
+
const command = await mdm.commands.get(c.req.param('id'));
|
|
684
|
+
if (!command) {
|
|
685
|
+
throw new HTTPException(404, { message: 'Command not found' });
|
|
686
|
+
}
|
|
687
|
+
return c.json(command);
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
// Send command
|
|
691
|
+
commands.post('/', async (c) => {
|
|
692
|
+
const body = await c.req.json<SendCommandInput>();
|
|
693
|
+
const command = await mdm.commands.send(body);
|
|
694
|
+
return c.json(command, 201);
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
// Cancel command
|
|
698
|
+
commands.post('/:id/cancel', async (c) => {
|
|
699
|
+
const command = await mdm.commands.cancel(c.req.param('id'));
|
|
700
|
+
return c.json(command);
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
app.route('/commands', commands);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// ============================================
|
|
707
|
+
// Event Routes
|
|
708
|
+
// ============================================
|
|
709
|
+
|
|
710
|
+
if (routes.events) {
|
|
711
|
+
const events = new Hono<MDMEnv>();
|
|
712
|
+
|
|
713
|
+
if (options.enableAuth) {
|
|
714
|
+
events.use('/*', adminAuth);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// List events
|
|
718
|
+
events.get('/', async (c) => {
|
|
719
|
+
const filter = {
|
|
720
|
+
deviceId: c.req.query('deviceId'),
|
|
721
|
+
type: c.req.query('type') as any,
|
|
722
|
+
limit: c.req.query('limit') ? parseInt(c.req.query('limit')!) : undefined,
|
|
723
|
+
offset: c.req.query('offset') ? parseInt(c.req.query('offset')!) : undefined,
|
|
724
|
+
};
|
|
725
|
+
|
|
726
|
+
const result = await mdm.db.listEvents(filter);
|
|
727
|
+
return c.json({ events: result });
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
app.route('/events', events);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// ============================================
|
|
734
|
+
// Health Check
|
|
735
|
+
// ============================================
|
|
736
|
+
|
|
737
|
+
app.get('/health', (c) => {
|
|
738
|
+
return c.json({
|
|
739
|
+
status: 'ok',
|
|
740
|
+
version: '0.1.0',
|
|
741
|
+
timestamp: new Date().toISOString(),
|
|
742
|
+
});
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
return app;
|
|
746
|
+
}
|