ink-framework-notifications 0.1.0-alpha.12 → 0.1.0-alpha.13

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/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "ink-framework-notifications",
3
- "version": "0.1.0-alpha.12",
3
+ "version": "0.1.0-alpha.13",
4
4
  "exports": {
5
- ".": "./src/index.ts"
5
+ ".": "./src/index.ts",
6
+ "./commands": "./src/commands.ts"
6
7
  },
7
8
  "peerDependencies": {
8
- "ink": "npm:ink-framework@0.1.0-alpha.12",
9
+ "ink": "npm:ink-framework@0.1.0-alpha.13",
9
10
  "react": "19.2.8"
10
11
  },
11
12
  "license": "MIT",
@@ -17,6 +18,7 @@
17
18
  "files": [
18
19
  "src",
19
20
  "*.json",
20
- "*.ts"
21
+ "*.ts",
22
+ "*.md"
21
23
  ]
22
24
  }
@@ -0,0 +1,34 @@
1
+ import { callNative, NativeError } from "ink/native";
2
+ import type { Destination } from "ink";
3
+
4
+ export interface NotificationContent {
5
+ id: string;
6
+ title: string;
7
+ body: string;
8
+ href?: Destination;
9
+ data?: string;
10
+ }
11
+ async function command(operation: string, payload: unknown): Promise<void> {
12
+ const value: unknown = JSON.parse(await callNative("notifications", operation, payload));
13
+ if (typeof value !== "object" || value === null || !("status" in value)) throw new NativeError("protocol", "Invalid notification result");
14
+ if (value.status === "idle") return;
15
+ if (value.status !== "error" || !("error" in value)) throw new NativeError("protocol", "Invalid notification result");
16
+ throw notificationError(value.error);
17
+ }
18
+ export function notificationError(value: unknown): NativeError {
19
+ if (typeof value !== "object" || value === null
20
+ || !("kind" in value) || typeof value.kind !== "string"
21
+ || !("message" in value) || typeof value.message !== "string") {
22
+ throw new NativeError("protocol", "Invalid notification error");
23
+ }
24
+ return new NativeError(value.kind, value.message, "retryable" in value && value.retryable === true);
25
+ }
26
+ export const notifications = {
27
+ show(notification: NotificationContent) { return command("schedule", { ...notification, delayMs: 0 }); },
28
+ schedule({ at, ...notification }: NotificationContent & { at: number; exact?: boolean }) {
29
+ if (!Number.isSafeInteger(at) || at < 0) return Promise.reject(new RangeError("Notification time must be a non-negative timestamp in milliseconds"));
30
+ return command("schedule", { ...notification, triggerAtMs: at });
31
+ },
32
+ cancel(id: string) { return command("cancel", { id }); },
33
+ };
34
+
package/src/hooks.ts ADDED
@@ -0,0 +1,38 @@
1
+ import { useEffect, useRef, useState, useCallback } from "ink/internal/react";
2
+ import { NativeError } from "ink/native";
3
+ import { attachNativeController } from "ink/native/controller";
4
+ import { notificationError } from "./commands";
5
+
6
+ export interface NotificationTap { id: string; data: string }
7
+ type TapState = { status: "loading" } | { status: "empty" } | { status: "ready"; value: NotificationTap } | { status: "error"; error: Error };
8
+ function decodeTap(value: unknown): TapState {
9
+ if (typeof value !== "object" || value === null || !("status" in value)) throw new NativeError("protocol", "Invalid notification tap");
10
+ if (value.status === "empty") return { status: "empty" };
11
+ if (value.status === "error" && "error" in value) return { status: "error", error: notificationError(value.error) };
12
+ if (value.status !== "ready" || !("value" in value) || typeof value.value !== "object" || value.value === null
13
+ || !("id" in value.value) || typeof value.value.id !== "string"
14
+ || !("data" in value.value) || typeof value.value.data !== "string") throw new NativeError("protocol", "Invalid notification tap");
15
+ return { status: "ready", value: { id: value.value.id, data: value.value.data } };
16
+ }
17
+ export function useNotificationTap() {
18
+ const [state, setState] = useState<TapState>({ status: "loading" });
19
+ const controller = useRef<ReturnType<typeof attachNativeController> | null>(null);
20
+ useEffect(() => {
21
+ setState({ status: "loading" });
22
+ const fail = (error: unknown) => setState({ status: "error", error: error instanceof Error ? error : new Error(String(error)) });
23
+ const attachment = attachNativeController("notifications", { kind: "notification-tap" }, value => {
24
+ try { setState(decodeTap(value)); } catch (error) { fail(error); }
25
+ });
26
+ controller.current = attachment;
27
+ void attachment.ready.catch(error => { if (controller.current === attachment) fail(error); });
28
+ return () => {
29
+ controller.current = null;
30
+ void attachment.dispose().catch(error => console.error("Could not release notification taps", error));
31
+ };
32
+ }, []);
33
+ const consume = useCallback(async () => {
34
+ if (!controller.current) throw new NativeError("unavailable", "Notification taps are not attached");
35
+ await controller.current.call("consume");
36
+ }, []);
37
+ return { state, consume };
38
+ }
package/src/index.ts CHANGED
@@ -1,69 +1,2 @@
1
- import { useEffect, useRef, useState, useCallback } from "react";
2
- import { callNative, NativeError } from "ink/native";
3
- import { attachNativeController } from "ink/native/controller";
4
- import type { Destination } from "ink";
5
-
6
- export interface NotificationContent {
7
- id: string;
8
- title: string;
9
- body: string;
10
- href?: Destination;
11
- data?: string;
12
- }
13
- async function command(operation: string, payload: unknown): Promise<void> {
14
- const value: unknown = JSON.parse(await callNative("notifications", operation, payload));
15
- if (typeof value !== "object" || value === null || !("status" in value)) throw new NativeError("protocol", "Invalid notification result");
16
- if (value.status === "idle") return;
17
- if (value.status !== "error" || !("error" in value)) throw new NativeError("protocol", "Invalid notification result");
18
- throw notificationError(value.error);
19
- }
20
- function notificationError(value: unknown): NativeError {
21
- if (typeof value !== "object" || value === null
22
- || !("kind" in value) || typeof value.kind !== "string"
23
- || !("message" in value) || typeof value.message !== "string") {
24
- throw new NativeError("protocol", "Invalid notification error");
25
- }
26
- return new NativeError(value.kind, value.message, "retryable" in value && value.retryable === true);
27
- }
28
- export const notifications = {
29
- show(notification: NotificationContent) { return command("schedule", { ...notification, delayMs: 0 }); },
30
- schedule({ at, ...notification }: NotificationContent & { at: number; exact?: boolean }) {
31
- if (!Number.isSafeInteger(at) || at < 0) return Promise.reject(new RangeError("Notification time must be a non-negative timestamp in milliseconds"));
32
- return command("schedule", { ...notification, triggerAtMs: at });
33
- },
34
- cancel(id: string) { return command("cancel", { id }); },
35
- };
36
-
37
- export interface NotificationTap { id: string; data: string }
38
- type TapState = { status: "loading" } | { status: "empty" } | { status: "ready"; value: NotificationTap } | { status: "error"; error: Error };
39
- function decodeTap(value: unknown): TapState {
40
- if (typeof value !== "object" || value === null || !("status" in value)) throw new NativeError("protocol", "Invalid notification tap");
41
- if (value.status === "empty") return { status: "empty" };
42
- if (value.status === "error" && "error" in value) return { status: "error", error: notificationError(value.error) };
43
- if (value.status !== "ready" || !("value" in value) || typeof value.value !== "object" || value.value === null
44
- || !("id" in value.value) || typeof value.value.id !== "string"
45
- || !("data" in value.value) || typeof value.value.data !== "string") throw new NativeError("protocol", "Invalid notification tap");
46
- return { status: "ready", value: { id: value.value.id, data: value.value.data } };
47
- }
48
- export function useNotificationTap() {
49
- const [state, setState] = useState<TapState>({ status: "loading" });
50
- const controller = useRef<ReturnType<typeof attachNativeController> | null>(null);
51
- useEffect(() => {
52
- setState({ status: "loading" });
53
- const fail = (error: unknown) => setState({ status: "error", error: error instanceof Error ? error : new Error(String(error)) });
54
- const attachment = attachNativeController("notifications", { kind: "notification-tap" }, value => {
55
- try { setState(decodeTap(value)); } catch (error) { fail(error); }
56
- });
57
- controller.current = attachment;
58
- void attachment.ready.catch(error => { if (controller.current === attachment) fail(error); });
59
- return () => {
60
- controller.current = null;
61
- void attachment.dispose().catch(error => console.error("Could not release notification taps", error));
62
- };
63
- }, []);
64
- const consume = useCallback(async () => {
65
- if (!controller.current) throw new NativeError("unavailable", "Notification taps are not attached");
66
- await controller.current.call("consume");
67
- }, []);
68
- return { state, consume };
69
- }
1
+ export { notifications, type NotificationContent } from "./commands";
2
+ export { useNotificationTap, type NotificationTap } from "./hooks";