-
Notifications
You must be signed in to change notification settings - Fork 2
/
notification.ts
292 lines (266 loc) · 8.49 KB
/
notification.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
import { notify_send } from "./plugin.ts";
type PlatformFeature<Platform extends boolean, FunctionType> = Platform extends
true ? FunctionType : never;
// Resources
// Web Notifications API: https://developer.mozilla.org/en-US/docs/Web/API/Notification
// Notify-rust API: https://github.com/hoodie/notify-rust/blob/main/src/notification.rs
export type MacSoundNames =
| "Basso"
| "Frog"
| "Hero"
| "Pop"
| "Submarine"
| "Blow"
| "Funk"
| "Morse"
| "Purr"
| "Tink"
| "Bottle"
| "Glass"
| "Ping"
| "Sosumi";
export class Notification<
MacOS extends boolean = false,
Windows extends boolean = false,
Linux extends boolean = false,
> {
/**
* Which platform-specific features to support in this notification.
*/
public readonly supports: { macos: MacOS; windows: Windows; linux: Linux };
/**
* Whether or not to error if a feature is called on an operating system that does not support it.
*/
public readonly strictSupport: boolean;
private _title: string | null = null;
private _subtitle: string | null = null;
private _body: string | null = null;
private _icon: string | null = null;
private _soundName: string | null = null;
private _timeout:
| { type: "Never" }
| { type: "Milliseconds"; value: number }
| null = null;
/**
* Create a Notification.
* Most fields are empty by default.
*
* Platform specific-features are locked behind the `supports` parameter.
*
* @param supports Which platform-specific features to support in this notification.
* @param strictSupport Whether or not to error if a feature is called on an operating system that does not support it.
*
* @example
* ```ts
* // By default, no platform-specific features are allowed.
* const n1 = new Notification();
*
* // Allow macos-specific features.
* // This will throw if a macos-specific feature is called on non-macos platforms.
* const n2 = new Notification({ macos: true });
*
* // Allow macos-specific features.
* // This will ignore macos-specific features on non-macos platforms.
* const n3 = new Notification({ macos: true }, false);
* ```
*/
public constructor(
{
macos = false as MacOS,
windows = false as Windows,
linux = false as Linux,
}: { macos?: MacOS; windows?: Windows; linux?: Linux } = {},
strictSupport: boolean = true,
) {
this.supports = {
macos,
windows,
linux,
};
this.strictSupport = strictSupport;
}
/**
* Set the `title`.
* This is a required field.
* Available on all platforms.
*
* For more elaborate content, use the `body` field.
*
* @param title
*/
public title = (title: string): this => {
this._title = title;
return this;
};
/**
* Set the `subtitle`.
* Available on macOS and Windows.
*
* For more elaborate content, use the `body` field.
*
* @param subtitle
*/
public subtitle = ((subtitle: string): this => {
if (this.#verifyPlatform(["macos", "windows"], "subtitle") === false) {
return this;
}
this._subtitle = subtitle;
return this;
}) as PlatformFeature<MacOS | Windows, (subtitle: string) => this>;
/**
* Set the `body`.
* Available on all platforms.
*
* Multiline textual content of the notification.
* Each line should be treated as a paragraph.
* Simple html markup may be supported on some platforms.
*
* @param body
*/
public body = (body: string): this => {
this._body = body;
return this;
};
/**
* Set the `icon`.
* Available on Linux.
*
* Can either be a file:// URI,
* or a common icon name, usually those in `/usr/share/icons`
* can all be used (or freedesktop.org names).
*
* @param icon
*/
public icon = ((icon: string): this => {
if (this.#verifyPlatform(["linux"], "icon") === false) return this;
this._icon = icon;
return this;
}) as PlatformFeature<Linux, (icon: string) => this>;
/**
* Set the `soundName` to play with the notification.
*
* With macOS support, a list of default sound names is provided.
*
* @param soundName
*/
public soundName = (
soundName: MacOS extends true ? MacSoundNames : string,
): this => {
this._soundName = soundName;
return this;
};
/**
* Set the `timeout`.
* Available on Windows and Linux.
*
* This sets the time (in milliseconds) from the time the notification is displayed
* until it is closed again by the Notification Server.
*
* Setting this to `'never'` will cause the notification to never expire.
*
* @param timeout
*/
public timeout = ((timeout: "never" | number): this => {
if (this.#verifyPlatform(["windows", "linux"], "timeout") === false) {
return this;
}
// TODO: Maybe compile-time check for positive numbers (once TS supports it)
if (typeof timeout === "number" && timeout <= 0) {
throw new Error(
"Notification timeout must be a number greater than 0 (or 'never').",
);
}
if (timeout === "never") {
this._timeout = { type: "Never" };
} else if (typeof timeout === "number") {
this._timeout = { type: "Milliseconds", value: timeout };
}
return this;
}) as PlatformFeature<Windows | Linux, (timeout: "never" | number) => this>;
/**
* Display the notification to the user.
*
* @throws When an error occurs while sending the notification.
*/
public show = () => {
// Check has minimum requirements to be sent
if (this.#verifyCanBeSent() !== true) return;
const json = JSON.stringify(this);
const result = notify_send(json);
if (result.type === "error") {
throw new Error(`${result.message} (when: ${result.when})`);
}
};
/**
* Clone the notification into a separate instance, maintaining all the properties.
*
* @returns The new notification instance.
*/
public clone = (): Notification<MacOS, Windows, Linux> => {
return Object.assign(
Object.create(Object.getPrototypeOf(this)),
this,
) as Notification<MacOS, Windows, Linux>;
};
/**
* Verify whether or not the notification has the minimum required fields to be sent.
*
* @throws If the notification cannot be sent with the appropriate explanation error.
* @returns True if the notification can be sent.
*/
#verifyCanBeSent = (): boolean => {
// Notification NEEDS at least a title
if (this._title === null) {
throw new Error(`Notification instance must have a title.`);
}
return true;
};
/**
* Verify whether a feature meant for a given platform should be run on the current platform.
*
* @param requestedPlatforms The requested platform to verify against the current platform.
* @throws If the platform is not supported by the notification instance.
* @throws If the feature should not be run (while in strict mode).
* @returns True if the feature should be run, false if the feature should not be run (while in non-strict mode).
*/
#verifyPlatform = (
requestedPlatforms: ("macos" | "linux" | "windows")[],
featureName: string,
): boolean => {
const currentPlatform = Deno.build.os === "darwin"
? "macos"
: Deno.build.os;
// List of platforms that are supported by the notification instance
const supportedPlatforms = requestedPlatforms.filter((platform) =>
this.supports[platform]
);
// If the requested platforms are not supported by this Notification instance, throw an error
if (supportedPlatforms.length === 0) {
const unsupportedPlatforms = requestedPlatforms.filter((platform) =>
this.supports[platform] === false
);
throw new Error(
`Notification instance does not explicitly support ${
unsupportedPlatforms.join(", or ")
}.`,
);
}
// Whether or not the oen of the requested & supported platform is the current platform
const isPlatformValid = supportedPlatforms.some((platform) => (
currentPlatform === platform
));
// If we're not strictly checking for support, just return whether or not the platform is valid
if (this.strictSupport === false) {
return isPlatformValid;
}
// Otherwise, we are strictly checking for support
// If we are strictly checking for support, and the platform is not valid, throw an error
if (isPlatformValid === false) {
throw new Error(
`Current operating system (${currentPlatform}) does not support ${featureName}.`,
);
}
// Otherwise, we are strictly checking for support AND the platform is valid
return true;
};
}