@procedures/notify 0.55.1 → 0.56.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/CHANGELOG.md +22 -0
- package/README.md +63 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/procedures.d.ts +1 -0
- package/dist/procedures.d.ts.map +1 -1
- package/dist/procedures.js +3 -1
- package/dist/procedures.js.map +1 -1
- package/dist/pushover-procedures.d.ts +20 -0
- package/dist/pushover-procedures.d.ts.map +1 -0
- package/dist/pushover-procedures.js +169 -0
- package/dist/pushover-procedures.js.map +1 -0
- package/dist/pushover.d.ts +247 -0
- package/dist/pushover.d.ts.map +1 -0
- package/dist/pushover.js +350 -0
- package/dist/pushover.js.map +1 -0
- package/package.json +18 -6
- package/src/index.ts +2 -0
- package/src/procedures.ts +3 -1
- package/src/pushover-procedures.ts +229 -0
- package/src/pushover.ts +530 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Pushover procedures of `@procedures/notify`: `notify.pushover.send`, `notify.pushover.receipt`,
|
|
3
|
+
* `notify.pushover.cancel`, `notify.pushover.validate` and `notify.pushover.limits`.
|
|
4
|
+
*
|
|
5
|
+
* Each procedure reads the API token from an environment variable, not from its input. Each one
|
|
6
|
+
* gives the result of its extractor: a failure is a value with `ok: false`, not an error.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { createProcedure, type AnyProcedure, type ProcedureContext } from "@procedures/client";
|
|
10
|
+
import * as z from "zod";
|
|
11
|
+
import {
|
|
12
|
+
PUSHOVER_EXPIRE_MAX_MS,
|
|
13
|
+
PUSHOVER_KEY as KEY,
|
|
14
|
+
PUSHOVER_LIMITS,
|
|
15
|
+
PUSHOVER_RETRY_MIN_MS,
|
|
16
|
+
callPushover,
|
|
17
|
+
extractPushoverCancelled,
|
|
18
|
+
extractPushoverLimits,
|
|
19
|
+
extractPushoverReceipt,
|
|
20
|
+
extractPushoverSent,
|
|
21
|
+
extractPushoverValidated,
|
|
22
|
+
pushoverCancelByTagRequest,
|
|
23
|
+
pushoverCancelRequest,
|
|
24
|
+
pushoverLimitsRequest,
|
|
25
|
+
pushoverReceiptRequest,
|
|
26
|
+
pushoverSendRequest,
|
|
27
|
+
pushoverValidateRequest,
|
|
28
|
+
type PushoverFailure,
|
|
29
|
+
type PushoverPriority,
|
|
30
|
+
type PushoverRequest,
|
|
31
|
+
type PushoverResponse,
|
|
32
|
+
} from "./pushover.js";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The names of the variables that `tokenEnv` and `userEnv` can name: `PUSHOVER_`, then capital
|
|
36
|
+
* letters, digits and underscores. Thus a caller cannot send another secret of the host to Pushover.
|
|
37
|
+
*/
|
|
38
|
+
export const PUSHOVER_ENV: RegExp = /^PUSHOVER_[A-Z0-9_]+$/;
|
|
39
|
+
|
|
40
|
+
const envName = (fallback: string, what: string): z.ZodDefault<z.ZodString> =>
|
|
41
|
+
z
|
|
42
|
+
.string()
|
|
43
|
+
.regex(PUSHOVER_ENV, `${what} names a variable that starts with PUSHOVER_, for example ${fallback}`)
|
|
44
|
+
.default(fallback)
|
|
45
|
+
.describe(`The name of the environment variable with the ${what === "tokenEnv" ? "API token of the application" : "user key"} (default: ${fallback})`);
|
|
46
|
+
|
|
47
|
+
const common = {
|
|
48
|
+
tokenEnv: envName("PUSHOVER_APP_TOKEN", "tokenEnv"),
|
|
49
|
+
timeoutMs: z.number().int().positive().max(120_000).default(20_000).describe("The time limit, in milliseconds (default: 20000)"),
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/** A result of a Pushover procedure: `ok`, and the fields of the success or of the failure. */
|
|
53
|
+
const ResultSchema = z.looseObject({ ok: z.boolean() });
|
|
54
|
+
type Result = z.infer<typeof ResultSchema>;
|
|
55
|
+
|
|
56
|
+
function secret(name: string): string {
|
|
57
|
+
const value = process.env[name]?.trim();
|
|
58
|
+
if (!value) throw new Error(`Set the environment variable ${name}`);
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function call<Success extends object>(
|
|
63
|
+
request: PushoverRequest,
|
|
64
|
+
extract: (response: PushoverResponse) => Success | PushoverFailure,
|
|
65
|
+
timeoutMs: number,
|
|
66
|
+
ctx: ProcedureContext | undefined,
|
|
67
|
+
): Promise<Result> {
|
|
68
|
+
return (await callPushover(fetch, request, extract, { timeoutMs, signal: ctx?.signal })) as unknown as Result;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const SendInputSchema = z.object({
|
|
72
|
+
user: z.string().optional().describe("The user key or the group key of the recipient. Without it, the procedure reads userEnv."),
|
|
73
|
+
userEnv: envName("PUSHOVER_USER_KEY", "userEnv"),
|
|
74
|
+
message: z.string().min(1).describe(`The text, ${PUSHOVER_LIMITS.message} characters at most`),
|
|
75
|
+
title: z.string().optional().describe(`The title, ${PUSHOVER_LIMITS.title} characters at most`),
|
|
76
|
+
url: z.string().optional().describe("An address that the notification opens"),
|
|
77
|
+
urlTitle: z.string().optional().describe("The text of the address"),
|
|
78
|
+
priority: z
|
|
79
|
+
.union([z.literal(-2), z.literal(-1), z.literal(0), z.literal(1), z.literal(2)])
|
|
80
|
+
.default(0)
|
|
81
|
+
.describe("-2 no notification, -1 no sound, 0 normal, 1 high, 2 emergency: it repeats until acknowledged (default: 0)"),
|
|
82
|
+
retryMs: z.number().int().min(PUSHOVER_RETRY_MIN_MS).optional().describe("Priority 2: the time between two repeats, 30000 or more"),
|
|
83
|
+
expireMs: z.number().int().positive().max(PUSHOVER_EXPIRE_MAX_MS).optional().describe("Priority 2: the time after which the repeats stop, 10800000 at most"),
|
|
84
|
+
callback: z.url().optional().describe("Priority 2: an address that gets a POST when the user acknowledges"),
|
|
85
|
+
tags: z.array(z.string().min(1)).max(20).optional().describe("Priority 2: tags, for notify.pushover.cancel with tag"),
|
|
86
|
+
sound: z.string().optional().describe("The name of a sound"),
|
|
87
|
+
devices: z.array(z.string()).optional().describe("The names of the devices that get the message (default: each device)"),
|
|
88
|
+
format: z.enum(["text", "html", "monospace"]).default("text").describe("The format of the text (default: text)"),
|
|
89
|
+
ttlMs: z.number().int().min(1000).optional().describe("The time after which the device deletes the message. Priority 2 ignores it."),
|
|
90
|
+
...common,
|
|
91
|
+
});
|
|
92
|
+
type SendInput = z.infer<typeof SendInputSchema>;
|
|
93
|
+
|
|
94
|
+
export const pushoverSendProcedure: AnyProcedure = createProcedure()
|
|
95
|
+
.path(["notify", "pushover", "send"])
|
|
96
|
+
.input(SendInputSchema)
|
|
97
|
+
.output(ResultSchema)
|
|
98
|
+
.meta({
|
|
99
|
+
description: "Send a Pushover message. Priority 2 repeats until the user acknowledges it, and gives a receipt.",
|
|
100
|
+
args: ["message"],
|
|
101
|
+
output: "json",
|
|
102
|
+
// It adds a message and deletes nothing. A second call sends a second message.
|
|
103
|
+
effects: { reads: [], writes: ["network"], destructive: false },
|
|
104
|
+
})
|
|
105
|
+
.handler(async (input: SendInput, ctx?: ProcedureContext): Promise<Result> => {
|
|
106
|
+
const priority = input.priority as PushoverPriority;
|
|
107
|
+
if (priority === 2 && (input.retryMs === undefined || input.expireMs === undefined)) throw new Error("Priority 2 needs retryMs and expireMs");
|
|
108
|
+
const user = input.user ?? secret(input.userEnv);
|
|
109
|
+
const request = pushoverSendRequest(secret(input.tokenEnv), {
|
|
110
|
+
user,
|
|
111
|
+
message: input.message,
|
|
112
|
+
title: input.title,
|
|
113
|
+
url: input.url,
|
|
114
|
+
urlTitle: input.urlTitle,
|
|
115
|
+
priority,
|
|
116
|
+
emergency:
|
|
117
|
+
priority === 2 && input.retryMs !== undefined && input.expireMs !== undefined
|
|
118
|
+
? { retryMs: input.retryMs, expireMs: input.expireMs, callback: input.callback, tags: input.tags }
|
|
119
|
+
: undefined,
|
|
120
|
+
sound: input.sound,
|
|
121
|
+
devices: input.devices,
|
|
122
|
+
format: input.format,
|
|
123
|
+
ttlMs: input.ttlMs,
|
|
124
|
+
});
|
|
125
|
+
return call(request, extractPushoverSent, input.timeoutMs, ctx);
|
|
126
|
+
})
|
|
127
|
+
.build();
|
|
128
|
+
|
|
129
|
+
const ReceiptInputSchema = z.object({
|
|
130
|
+
receipt: z.string().regex(KEY, "A receipt is 30 letters and digits").describe("The receipt of an emergency message"),
|
|
131
|
+
...common,
|
|
132
|
+
});
|
|
133
|
+
type ReceiptInput = z.infer<typeof ReceiptInputSchema>;
|
|
134
|
+
|
|
135
|
+
export const pushoverReceiptProcedure: AnyProcedure = createProcedure()
|
|
136
|
+
.path(["notify", "pushover", "receipt"])
|
|
137
|
+
.input(ReceiptInputSchema)
|
|
138
|
+
.output(ResultSchema)
|
|
139
|
+
.meta({
|
|
140
|
+
description: "Read the receipt of a Pushover emergency message: acknowledged, by which device, expired. Read it at most once in 5 s.",
|
|
141
|
+
args: ["receipt"],
|
|
142
|
+
output: "json",
|
|
143
|
+
effects: { reads: ["network"], writes: [], destructive: false },
|
|
144
|
+
})
|
|
145
|
+
.handler(async (input: ReceiptInput, ctx?: ProcedureContext): Promise<Result> =>
|
|
146
|
+
call(pushoverReceiptRequest(secret(input.tokenEnv), input.receipt), extractPushoverReceipt, input.timeoutMs, ctx),
|
|
147
|
+
)
|
|
148
|
+
.build();
|
|
149
|
+
|
|
150
|
+
const CancelInputSchema = z
|
|
151
|
+
.object({
|
|
152
|
+
receipt: z.string().regex(KEY, "A receipt is 30 letters and digits").optional().describe("The receipt of the message to stop"),
|
|
153
|
+
tag: z.string().min(1).optional().describe("Stop each emergency message of the application with this tag"),
|
|
154
|
+
...common,
|
|
155
|
+
})
|
|
156
|
+
.refine((input) => (input.receipt === undefined) !== (input.tag === undefined), { message: "Give receipt or tag, not both" });
|
|
157
|
+
type CancelInput = z.infer<typeof CancelInputSchema>;
|
|
158
|
+
|
|
159
|
+
export const pushoverCancelProcedure: AnyProcedure = createProcedure()
|
|
160
|
+
.path(["notify", "pushover", "cancel"])
|
|
161
|
+
.input(CancelInputSchema)
|
|
162
|
+
.output(ResultSchema)
|
|
163
|
+
.meta({
|
|
164
|
+
description: "Stop the repeats of a Pushover emergency message, by its receipt or by a tag",
|
|
165
|
+
args: [],
|
|
166
|
+
output: "json",
|
|
167
|
+
// It stops the repeats and deletes no message. A second cancel has no effect.
|
|
168
|
+
effects: { reads: [], writes: ["network"], destructive: false, idempotent: true },
|
|
169
|
+
})
|
|
170
|
+
.handler(async (input: CancelInput, ctx?: ProcedureContext): Promise<Result> => {
|
|
171
|
+
const token = secret(input.tokenEnv);
|
|
172
|
+
const request = input.receipt !== undefined ? pushoverCancelRequest(token, input.receipt) : pushoverCancelByTagRequest(token, input.tag ?? "");
|
|
173
|
+
return call(request, extractPushoverCancelled, input.timeoutMs, ctx);
|
|
174
|
+
})
|
|
175
|
+
.build();
|
|
176
|
+
|
|
177
|
+
const ValidateInputSchema = z.object({
|
|
178
|
+
user: z.string().optional().describe("The user key to examine. Without it, the procedure reads userEnv."),
|
|
179
|
+
userEnv: envName("PUSHOVER_USER_KEY", "userEnv"),
|
|
180
|
+
device: z.string().optional().describe("The name of a device to examine"),
|
|
181
|
+
...common,
|
|
182
|
+
});
|
|
183
|
+
type ValidateInput = z.infer<typeof ValidateInputSchema>;
|
|
184
|
+
|
|
185
|
+
export const pushoverValidateProcedure: AnyProcedure = createProcedure()
|
|
186
|
+
.path(["notify", "pushover", "validate"])
|
|
187
|
+
.input(ValidateInputSchema)
|
|
188
|
+
.output(ResultSchema)
|
|
189
|
+
.meta({
|
|
190
|
+
description: "Examine a Pushover user key: its active devices and licenses. It sends no message.",
|
|
191
|
+
args: [],
|
|
192
|
+
output: "json",
|
|
193
|
+
effects: { reads: ["network"], writes: [], destructive: false },
|
|
194
|
+
})
|
|
195
|
+
.handler(async (input: ValidateInput, ctx?: ProcedureContext): Promise<Result> =>
|
|
196
|
+
call(
|
|
197
|
+
pushoverValidateRequest(secret(input.tokenEnv), input.user ?? secret(input.userEnv), input.device),
|
|
198
|
+
extractPushoverValidated,
|
|
199
|
+
input.timeoutMs,
|
|
200
|
+
ctx,
|
|
201
|
+
),
|
|
202
|
+
)
|
|
203
|
+
.build();
|
|
204
|
+
|
|
205
|
+
const LimitsInputSchema = z.object({ ...common });
|
|
206
|
+
type LimitsInput = z.infer<typeof LimitsInputSchema>;
|
|
207
|
+
|
|
208
|
+
export const pushoverLimitsProcedure: AnyProcedure = createProcedure()
|
|
209
|
+
.path(["notify", "pushover", "limits"])
|
|
210
|
+
.input(LimitsInputSchema)
|
|
211
|
+
.output(ResultSchema)
|
|
212
|
+
.meta({
|
|
213
|
+
description: "Read the monthly message limit of the Pushover application, the messages left and the reset time",
|
|
214
|
+
args: [],
|
|
215
|
+
output: "json",
|
|
216
|
+
effects: { reads: ["network"], writes: [], destructive: false },
|
|
217
|
+
})
|
|
218
|
+
.handler(async (input: LimitsInput, ctx?: ProcedureContext): Promise<Result> =>
|
|
219
|
+
call(pushoverLimitsRequest(secret(input.tokenEnv)), extractPushoverLimits, input.timeoutMs, ctx),
|
|
220
|
+
)
|
|
221
|
+
.build();
|
|
222
|
+
|
|
223
|
+
export const pushoverProcedures: AnyProcedure[] = [
|
|
224
|
+
pushoverSendProcedure,
|
|
225
|
+
pushoverReceiptProcedure,
|
|
226
|
+
pushoverCancelProcedure,
|
|
227
|
+
pushoverValidateProcedure,
|
|
228
|
+
pushoverLimitsProcedure,
|
|
229
|
+
];
|