1import type {
2 RelayAgentActivitySnapshotResponse,
3 RelayDeviceRegistrationRequest,
4 RelayLiveActivityRegistrationRequest,
5} from "@t3tools/contracts/relay";
6import * as DateTime from "effect/DateTime";
7import * as Context from "effect/Context";
8import * as Effect from "effect/Effect";
9import * as Layer from "effect/Layer";
10
11import * as AgentActivityRows from "./AgentActivityRows.ts";
12import * as Devices from "./Devices.ts";
13import * as LiveActivities from "./LiveActivities.ts";
14import * as AgentActivityPublisher from "./AgentActivityPublisher.ts";
15
16export type MobileRegistrationError =
17 | Devices.DeviceRegistrationPersistenceError
18 | Devices.DeviceUnregistrationPersistenceError
19 | LiveActivities.LiveActivityRegistrationPersistenceError
20 | AgentActivityRows.AgentActivityRowListPersistenceError;
21
22export class MobileRegistrations extends Context.Service<
23 MobileRegistrations,
24 {
25 readonly registerDevice: (input: {
26 readonly userId: string;
27 readonly payload: RelayDeviceRegistrationRequest;
28 }) => Effect.Effect<{ readonly ok: true }, MobileRegistrationError>;
29 readonly registerLiveActivity: (input: {
30 readonly userId: string;
31 readonly payload: RelayLiveActivityRegistrationRequest;
32 }) => Effect.Effect<{ readonly ok: true }, MobileRegistrationError>;
33 readonly unregisterDevice: (input: {
34 readonly userId: string;
35 readonly deviceId: string;
36 }) => Effect.Effect<{ readonly ok: true }, MobileRegistrationError>;
37 readonly getAgentActivitySnapshot: (input: {
38 readonly userId: string;
39 }) => Effect.Effect<RelayAgentActivitySnapshotResponse, MobileRegistrationError>;
40 }
41>()("t3code-relay/agentActivity/MobileRegistrations") {}
42
43export const make = Effect.gen(function* () {
44 const rows = yield* AgentActivityRows.AgentActivityRows;
45 const devices = yield* Devices.Devices;
46 const liveActivities = yield* LiveActivities.LiveActivities;
47 const publisher = yield* AgentActivityPublisher.AgentActivityPublisher;
48
49 return MobileRegistrations.of({
50 registerDevice: Effect.fn("relay.mobile_registrations.register_device")(function* (input) {
51 yield* Effect.annotateCurrentSpan({
52 "relay.mobile.device_id": input.payload.deviceId,
53 "relay.mobile.platform": input.payload.platform,
54 });
55 yield* devices.register({ userId: input.userId, registration: input.payload });
56 yield* publisher
57 .replayForLiveActivityRegistration({
58 userId: input.userId,
59 deviceId: input.payload.deviceId,
60 })
61 .pipe(
62 Effect.tapError((error) =>
63 Effect.logWarning("device registration activity replay failed", {
64 errorTag: error._tag,
65 }),
66 ),
67 Effect.ignore,
68 );
69 return { ok: true as const };
70 }),
71 registerLiveActivity: Effect.fn("relay.mobile_registrations.register_live_activity")(
72 function* (input) {
73 yield* Effect.annotateCurrentSpan({
74 "relay.mobile.device_id": input.payload.deviceId,
75 });
76 yield* liveActivities.register({ userId: input.userId, registration: input.payload });
77 yield* publisher
78 .replayForLiveActivityRegistration({
79 userId: input.userId,
80 deviceId: input.payload.deviceId,
81 })
82 .pipe(
83 Effect.tapError((error) =>
84 Effect.logWarning("live activity registration replay failed", {
85 errorTag: error._tag,
86 }),
87 ),
88 Effect.ignore,
89 );
90 return { ok: true as const };
91 },
92 ),
93 getAgentActivitySnapshot: Effect.fn("relay.mobile_registrations.get_agent_activity_snapshot")(
94 function* (input) {
95 const activeStates = yield* rows.listForUser({ userId: input.userId });
96 const now = yield* DateTime.now;
97 return {
98 aggregate: AgentActivityPublisher.makeAggregateState({
99 activeStates,
100 terminalState: null,
101 nowMs: now.epochMilliseconds,
102 }),
103 };
104 },
105 ),
106 unregisterDevice: Effect.fn("relay.mobile_registrations.unregister_device")(function* (input) {
107 yield* Effect.annotateCurrentSpan({
108 "relay.mobile.device_id": input.deviceId,
109 });
110 yield* devices.unregister(input);
111 return { ok: true as const };
112 }),
113 });
114});
115
116export const layer = Layer.effect(MobileRegistrations, make);