1import * as Notifications from "expo-notifications";
2import * as Effect from "effect/Effect";
3import * as Schema from "effect/Schema";
4import { Platform } from "react-native";
5
6export type NotificationPermissionResult =
7 | { readonly type: "unsupported" }
8 | { readonly type: "granted" }
9 | { readonly type: "denied"; readonly canAskAgain: boolean };
10
11export class NotificationPermissionReadError extends Schema.TaggedError<NotificationPermissionReadError>()(
12 "NotificationPermissionReadError",
13 {
14 cause: Schema.Defect(),
15 },
16) {
17 override get message(): string {
18 return "Failed to read notification permissions.";
19 }
20}
21
22export class NotificationPermissionRequestError extends Schema.TaggedError<NotificationPermissionRequestError>()(
23 "NotificationPermissionRequestError",
24 {
25 cause: Schema.Defect(),
26 },
27) {
28 override get message(): string {
29 return "Failed to request notification permissions.";
30 }
31}
32
33export const requestAgentNotificationPermission: Effect.Effect<
34 NotificationPermissionResult,
35 NotificationPermissionReadError | NotificationPermissionRequestError
36> = Effect.gen(function* () {
37 if (Platform.OS !== "ios" && Platform.OS !== "android") {
38 return { type: "unsupported" };
39 }
40
41 if (Platform.OS === "android") {
42 yield* Effect.tryPromise({
43 try: () =>
44 Notifications.setNotificationChannelAsync("agent-alerts", {
45 name: "Agent alerts",
46 importance: Notifications.AndroidImportance.HIGH,
47 }),
48 catch: (cause) => new NotificationPermissionRequestError({ cause }),
49 });
50 }
51
52 const existing = yield* Effect.tryPromise({
53 try: () => Notifications.getPermissionsAsync(),
54 catch: (cause) => new NotificationPermissionReadError({ cause }),
55 });
56 if (existing.granted) {
57 return { type: "granted" };
58 }
59
60 if (!existing.canAskAgain) {
61 return { type: "denied", canAskAgain: false };
62 }
63
64 const requested = yield* Effect.tryPromise({
65 try: () =>
66 Notifications.requestPermissionsAsync({
67 ios: {
68 allowAlert: true,
69 allowBadge: true,
70 allowSound: true,
71 },
72 }),
73 catch: (cause) => new NotificationPermissionRequestError({ cause }),
74 });
75 return requested.granted
76 ? { type: "granted" }
77 : { type: "denied", canAskAgain: requested.canAskAgain };
78});