feat(push): add phase7 offline push queue, worker, APIs, and simulator inbox

This commit is contained in:
2026-01-24 15:20:00 +00:00
parent bccc049fc3
commit 6d6c77f77e
9 changed files with 392 additions and 6 deletions

View File

@@ -9,6 +9,7 @@ import { deviceCommands, deviceLinks, devices, streamSessions } from '../db/sche
import { mediaProvider } from '../media/service';
import { requireDeviceAuth } from '../middleware/device-auth';
import { dispatchCommandById, sendRealtimeToDevice } from '../realtime/gateway';
import { enqueuePushNotification } from '../services/push';
import { createRecordingForStream } from './recordings';
const router = Router();
@@ -167,13 +168,25 @@ router.post('/request', requireDeviceAuth, async (req, res) => {
const refreshedCommand = await db.query.deviceCommands.findFirst({ where: eq(deviceCommands.id, command.id) });
sendRealtimeToDevice(sourceDevice.id, 'stream:requested', {
const deliveredToRequester = sendRealtimeToDevice(sourceDevice.id, 'stream:requested', {
streamSessionId: session.id,
cameraDeviceId: cameraDevice.id,
status: session.status,
reason: session.reason,
});
if (!deliveredToRequester) {
await enqueuePushNotification({
ownerUserId: sourceDevice.userId,
recipientDeviceId: sourceDevice.id,
type: 'stream_requested',
payload: {
streamSessionId: session.id,
cameraDeviceId: cameraDevice.id,
},
});
}
res.status(201).json({
message: 'Stream request sent',
streamSession: session,
@@ -251,7 +264,7 @@ router.post('/:streamSessionId/accept', requireDeviceAuth, async (req, res) => {
return;
}
sendRealtimeToDevice(session.requesterDeviceId, 'stream:started', {
const deliveredToRequester = sendRealtimeToDevice(session.requesterDeviceId, 'stream:started', {
streamSessionId: updated.id,
cameraDeviceId: updated.cameraDeviceId,
status: updated.status,
@@ -261,6 +274,18 @@ router.post('/:streamSessionId/accept', requireDeviceAuth, async (req, res) => {
subscribeEndpoint: updated.subscribeEndpoint,
});
if (!deliveredToRequester) {
await enqueuePushNotification({
ownerUserId: session.ownerUserId,
recipientDeviceId: session.requesterDeviceId,
type: 'stream_started',
payload: {
streamSessionId: updated.id,
cameraDeviceId: updated.cameraDeviceId,
},
});
}
res.json({ message: 'Stream accepted', streamSession: updated });
});
@@ -405,18 +430,42 @@ router.post('/:streamSessionId/end', requireDeviceAuth, async (req, res) => {
await createRecordingForStream(session.id);
sendRealtimeToDevice(session.requesterDeviceId, 'stream:ended', {
const deliveredToRequester = sendRealtimeToDevice(session.requesterDeviceId, 'stream:ended', {
streamSessionId: session.id,
status: parsed.data.reason,
endedAt: now,
});
sendRealtimeToDevice(session.cameraDeviceId, 'stream:ended', {
const deliveredToCamera = sendRealtimeToDevice(session.cameraDeviceId, 'stream:ended', {
streamSessionId: session.id,
status: parsed.data.reason,
endedAt: now,
});
if (!deliveredToRequester) {
await enqueuePushNotification({
ownerUserId: session.ownerUserId,
recipientDeviceId: session.requesterDeviceId,
type: 'stream_ended',
payload: {
streamSessionId: session.id,
status: parsed.data.reason,
},
});
}
if (!deliveredToCamera) {
await enqueuePushNotification({
ownerUserId: session.ownerUserId,
recipientDeviceId: session.cameraDeviceId,
type: 'stream_ended',
payload: {
streamSessionId: session.id,
status: parsed.data.reason,
},
});
}
res.json({ message: 'Stream ended', streamSession: updated });
});