@codingfactory/notify-kit-client 0.1.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/dist/chunk-7LQBNDRD.js +235 -0
- package/dist/chunk-7LQBNDRD.js.map +1 -0
- package/dist/index.d.ts +547 -0
- package/dist/index.js +737 -0
- package/dist/index.js.map +1 -0
- package/dist/nuxt/index.d.ts +114 -0
- package/dist/nuxt/index.js +55 -0
- package/dist/nuxt/index.js.map +1 -0
- package/dist/useNotifyKitModals-BZ_NiTKw.d.ts +586 -0
- package/package.json +77 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,737 @@
|
|
|
1
|
+
import { useNotifyKitStore } from './chunk-7LQBNDRD.js';
|
|
2
|
+
export { InvalidSnoozeDurationError, NOTIFY_KIT_MODALS_KEY, NOTIFY_KIT_STORE_KEY, provideNotifyKitModals, provideNotifyKitStore, resetNotifyKitStore, useNotifyKitModals, useNotifyKitStore } from './chunk-7LQBNDRD.js';
|
|
3
|
+
import { provide, inject, ref, computed, onMounted, onUnmounted, watch } from 'vue';
|
|
4
|
+
|
|
5
|
+
// src/types/notification.ts
|
|
6
|
+
var NOTIFICATION_CATEGORIES = [
|
|
7
|
+
"workspace",
|
|
8
|
+
"security",
|
|
9
|
+
"orders",
|
|
10
|
+
"social",
|
|
11
|
+
"system",
|
|
12
|
+
"marketing"
|
|
13
|
+
];
|
|
14
|
+
var NOTIFICATION_LEVELS = [
|
|
15
|
+
"info",
|
|
16
|
+
"success",
|
|
17
|
+
"warning",
|
|
18
|
+
"danger"
|
|
19
|
+
];
|
|
20
|
+
function isValidCategory(value) {
|
|
21
|
+
return typeof value === "string" && NOTIFICATION_CATEGORIES.includes(value);
|
|
22
|
+
}
|
|
23
|
+
function isValidLevel(value) {
|
|
24
|
+
return typeof value === "string" && NOTIFICATION_LEVELS.includes(value);
|
|
25
|
+
}
|
|
26
|
+
function isNotifyKitNotification(value) {
|
|
27
|
+
if (typeof value !== "object" || value === null) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
const obj = value;
|
|
31
|
+
if (typeof obj["id"] !== "string") return false;
|
|
32
|
+
if (typeof obj["key"] !== "string") return false;
|
|
33
|
+
if (typeof obj["title"] !== "string") return false;
|
|
34
|
+
if (typeof obj["body"] !== "string") return false;
|
|
35
|
+
if (typeof obj["created_at"] !== "string") return false;
|
|
36
|
+
if (!isValidCategory(obj["category"])) return false;
|
|
37
|
+
if (!isValidLevel(obj["level"])) return false;
|
|
38
|
+
if (obj["action_url"] !== null && typeof obj["action_url"] !== "string") return false;
|
|
39
|
+
if (obj["workspace_id"] !== null && typeof obj["workspace_id"] !== "string") return false;
|
|
40
|
+
if (obj["group_key"] !== null && typeof obj["group_key"] !== "string") return false;
|
|
41
|
+
if (obj["idempotency_key"] !== null && typeof obj["idempotency_key"] !== "string")
|
|
42
|
+
return false;
|
|
43
|
+
if (obj["read_at"] !== null && typeof obj["read_at"] !== "string") return false;
|
|
44
|
+
if (typeof obj["data"] !== "object" || obj["data"] === null) {
|
|
45
|
+
if (obj["data"] !== void 0 && (typeof obj["data"] !== "object" || obj["data"] === null)) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (obj["modal"] !== null && typeof obj["modal"] !== "object") return false;
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
function isModalEnabled(notification) {
|
|
53
|
+
return notification.modal?.enabled ?? false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/types/modal.ts
|
|
57
|
+
function isModalSpec(value) {
|
|
58
|
+
if (typeof value !== "object" || value === null) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
const obj = value;
|
|
62
|
+
if (typeof obj["enabled"] !== "boolean") return false;
|
|
63
|
+
if (typeof obj["requires_ack"] !== "boolean") return false;
|
|
64
|
+
if (!Array.isArray(obj["snooze_options_minutes"])) return false;
|
|
65
|
+
const snoozeOptions = obj["snooze_options_minutes"];
|
|
66
|
+
for (const option of snoozeOptions) {
|
|
67
|
+
if (typeof option !== "number" || !Number.isInteger(option) || option < 1) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/types/preferences.ts
|
|
75
|
+
function isChannelPreferences(value) {
|
|
76
|
+
if (typeof value !== "object" || value === null) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
const obj = value;
|
|
80
|
+
return typeof obj["database"] === "boolean" && typeof obj["broadcast"] === "boolean" && typeof obj["mail"] === "boolean";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/types/api.ts
|
|
84
|
+
function isPaginationMeta(value) {
|
|
85
|
+
if (typeof value !== "object" || value === null) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
const obj = value;
|
|
89
|
+
return typeof obj["current_page"] === "number" && typeof obj["last_page"] === "number" && typeof obj["per_page"] === "number" && typeof obj["total"] === "number";
|
|
90
|
+
}
|
|
91
|
+
function isMeResponse(value) {
|
|
92
|
+
if (typeof value !== "object" || value === null) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
const obj = value;
|
|
96
|
+
return typeof obj["broadcast_channel"] === "string";
|
|
97
|
+
}
|
|
98
|
+
function isUnreadCountResponse(value) {
|
|
99
|
+
if (typeof value !== "object" || value === null) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
const obj = value;
|
|
103
|
+
return typeof obj["count"] === "number";
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/client/NotifyKitClient.ts
|
|
107
|
+
var NotifyKitApiError = class extends Error {
|
|
108
|
+
constructor(message, status, errors) {
|
|
109
|
+
super(message);
|
|
110
|
+
this.status = status;
|
|
111
|
+
this.errors = errors;
|
|
112
|
+
}
|
|
113
|
+
name = "NotifyKitApiError";
|
|
114
|
+
};
|
|
115
|
+
var NotifyKitClient = class {
|
|
116
|
+
baseUrl;
|
|
117
|
+
getAuthHeaders;
|
|
118
|
+
fetchFn;
|
|
119
|
+
constructor(config) {
|
|
120
|
+
this.baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
121
|
+
this.getAuthHeaders = config.getAuthHeaders ?? void 0;
|
|
122
|
+
this.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
123
|
+
}
|
|
124
|
+
// ============================================
|
|
125
|
+
// Me
|
|
126
|
+
// ============================================
|
|
127
|
+
/**
|
|
128
|
+
* Get the current user's broadcast channel information.
|
|
129
|
+
*/
|
|
130
|
+
async getMe() {
|
|
131
|
+
return this.request("GET", "/me");
|
|
132
|
+
}
|
|
133
|
+
// ============================================
|
|
134
|
+
// Notifications
|
|
135
|
+
// ============================================
|
|
136
|
+
/**
|
|
137
|
+
* List notifications for the current user.
|
|
138
|
+
*/
|
|
139
|
+
async listNotifications(params) {
|
|
140
|
+
const queryParams = this.buildNotificationParams(params);
|
|
141
|
+
const queryString = queryParams.toString();
|
|
142
|
+
const url = queryString.length > 0 ? `/notifications?${queryString}` : "/notifications";
|
|
143
|
+
return this.request("GET", url);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Get the count of unread notifications.
|
|
147
|
+
*/
|
|
148
|
+
async getUnreadCount() {
|
|
149
|
+
return this.request(
|
|
150
|
+
"GET",
|
|
151
|
+
"/notifications/unread-count"
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Mark a notification as read.
|
|
156
|
+
*/
|
|
157
|
+
async markRead(id) {
|
|
158
|
+
await this.request("PATCH", `/notifications/${id}/read`);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Mark all notifications as read.
|
|
162
|
+
*/
|
|
163
|
+
async markAllRead() {
|
|
164
|
+
await this.request("POST", "/notifications/read-all");
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Delete a notification.
|
|
168
|
+
*/
|
|
169
|
+
async deleteNotification(id) {
|
|
170
|
+
await this.request("DELETE", `/notifications/${id}`);
|
|
171
|
+
}
|
|
172
|
+
// ============================================
|
|
173
|
+
// Groups (Phase 2)
|
|
174
|
+
// ============================================
|
|
175
|
+
/**
|
|
176
|
+
* Mark all notifications in a group as read.
|
|
177
|
+
*/
|
|
178
|
+
async markGroupRead(groupKey) {
|
|
179
|
+
await this.request("PATCH", `/notifications/groups/${groupKey}/read`);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Get all notifications in a group.
|
|
183
|
+
*/
|
|
184
|
+
async getGroupNotifications(groupKey, params) {
|
|
185
|
+
const queryParams = new URLSearchParams();
|
|
186
|
+
if (params?.page !== void 0) {
|
|
187
|
+
queryParams.set("page", String(params.page));
|
|
188
|
+
}
|
|
189
|
+
if (params?.per_page !== void 0) {
|
|
190
|
+
queryParams.set("per_page", String(params.per_page));
|
|
191
|
+
}
|
|
192
|
+
const queryString = queryParams.toString();
|
|
193
|
+
const url = queryString.length > 0 ? `/notifications/groups/${groupKey}?${queryString}` : `/notifications/groups/${groupKey}`;
|
|
194
|
+
return this.request("GET", url);
|
|
195
|
+
}
|
|
196
|
+
// ============================================
|
|
197
|
+
// Modals
|
|
198
|
+
// ============================================
|
|
199
|
+
/**
|
|
200
|
+
* List outstanding modal notifications.
|
|
201
|
+
*/
|
|
202
|
+
async listModals() {
|
|
203
|
+
const response = await this.request(
|
|
204
|
+
"GET",
|
|
205
|
+
"/modals"
|
|
206
|
+
);
|
|
207
|
+
return response.data;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Acknowledge a modal notification.
|
|
211
|
+
*/
|
|
212
|
+
async ackModal(id) {
|
|
213
|
+
await this.request("POST", `/modals/${id}/ack`);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Snooze a modal notification.
|
|
217
|
+
*/
|
|
218
|
+
async snoozeModal(id, minutes) {
|
|
219
|
+
await this.request("POST", `/modals/${id}/snooze`, { minutes });
|
|
220
|
+
}
|
|
221
|
+
// ============================================
|
|
222
|
+
// Preferences
|
|
223
|
+
// ============================================
|
|
224
|
+
/**
|
|
225
|
+
* Get the current user's notification preferences.
|
|
226
|
+
*/
|
|
227
|
+
async getPreferences() {
|
|
228
|
+
return this.request("GET", "/preferences");
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Update the current user's notification preferences.
|
|
232
|
+
*/
|
|
233
|
+
async updatePreferences(preferences) {
|
|
234
|
+
await this.request("PUT", "/preferences", preferences);
|
|
235
|
+
}
|
|
236
|
+
// ============================================
|
|
237
|
+
// Private helpers
|
|
238
|
+
// ============================================
|
|
239
|
+
buildNotificationParams(params) {
|
|
240
|
+
const queryParams = new URLSearchParams();
|
|
241
|
+
if (params === void 0) {
|
|
242
|
+
return queryParams;
|
|
243
|
+
}
|
|
244
|
+
if (params.unread === true) {
|
|
245
|
+
queryParams.set("unread", "true");
|
|
246
|
+
}
|
|
247
|
+
if (params.category !== void 0) {
|
|
248
|
+
queryParams.set("category", params.category);
|
|
249
|
+
}
|
|
250
|
+
if (params.workspace_id !== void 0) {
|
|
251
|
+
queryParams.set("workspace_id", params.workspace_id);
|
|
252
|
+
}
|
|
253
|
+
if (params.grouped === true) {
|
|
254
|
+
queryParams.set("grouped", "1");
|
|
255
|
+
}
|
|
256
|
+
if (params.page !== void 0) {
|
|
257
|
+
queryParams.set("page", String(params.page));
|
|
258
|
+
}
|
|
259
|
+
if (params.per_page !== void 0) {
|
|
260
|
+
queryParams.set("per_page", String(params.per_page));
|
|
261
|
+
}
|
|
262
|
+
return queryParams;
|
|
263
|
+
}
|
|
264
|
+
async request(method, path, body) {
|
|
265
|
+
const url = `${this.baseUrl}${path}`;
|
|
266
|
+
const headers = {
|
|
267
|
+
"Content-Type": "application/json",
|
|
268
|
+
Accept: "application/json"
|
|
269
|
+
};
|
|
270
|
+
if (this.getAuthHeaders !== void 0) {
|
|
271
|
+
const authHeaders = await this.getAuthHeaders();
|
|
272
|
+
Object.assign(headers, authHeaders);
|
|
273
|
+
}
|
|
274
|
+
const requestInit = {
|
|
275
|
+
method,
|
|
276
|
+
headers
|
|
277
|
+
};
|
|
278
|
+
if (body !== void 0) {
|
|
279
|
+
requestInit.body = JSON.stringify(body);
|
|
280
|
+
}
|
|
281
|
+
const response = await this.fetchFn(url, requestInit);
|
|
282
|
+
if (!response.ok) {
|
|
283
|
+
const errorData = await response.json();
|
|
284
|
+
throw new NotifyKitApiError(
|
|
285
|
+
errorData.message ?? `Request failed with status ${String(response.status)}`,
|
|
286
|
+
response.status,
|
|
287
|
+
errorData.errors
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
const text = await response.text();
|
|
291
|
+
if (text.length === 0) {
|
|
292
|
+
return void 0;
|
|
293
|
+
}
|
|
294
|
+
return JSON.parse(text);
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
function createNotifyKitClient(config) {
|
|
298
|
+
return new NotifyKitClient(config);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// src/client/endpoints.ts
|
|
302
|
+
var ENDPOINTS = {
|
|
303
|
+
/** GET - Returns broadcast channel for authenticated user */
|
|
304
|
+
ME: "/me",
|
|
305
|
+
/** GET - List notifications (supports filters and pagination) */
|
|
306
|
+
NOTIFICATIONS: "/notifications",
|
|
307
|
+
/** GET - Get count of unread notifications */
|
|
308
|
+
UNREAD_COUNT: "/notifications/unread-count",
|
|
309
|
+
/** PATCH - Mark a specific notification as read */
|
|
310
|
+
MARK_READ: (id) => `/notifications/${id}/read`,
|
|
311
|
+
/** POST - Mark all notifications as read */
|
|
312
|
+
MARK_ALL_READ: "/notifications/read-all",
|
|
313
|
+
/** DELETE - Delete a specific notification */
|
|
314
|
+
DELETE_NOTIFICATION: (id) => `/notifications/${id}`,
|
|
315
|
+
/** PATCH - Mark all notifications in a group as read (Phase 2) */
|
|
316
|
+
MARK_GROUP_READ: (groupKey) => `/notifications/groups/${groupKey}/read`,
|
|
317
|
+
/** GET - Get all notifications in a group (Phase 2) */
|
|
318
|
+
GROUP_NOTIFICATIONS: (groupKey) => `/notifications/groups/${groupKey}`,
|
|
319
|
+
/** GET - List outstanding modal notifications */
|
|
320
|
+
MODALS: "/modals",
|
|
321
|
+
/** POST - Acknowledge a modal notification */
|
|
322
|
+
ACK_MODAL: (id) => `/modals/${id}/ack`,
|
|
323
|
+
/** POST - Snooze a modal notification */
|
|
324
|
+
SNOOZE_MODAL: (id) => `/modals/${id}/snooze`,
|
|
325
|
+
/** GET - Get user's notification preferences */
|
|
326
|
+
PREFERENCES: "/preferences"
|
|
327
|
+
};
|
|
328
|
+
var NOTIFY_KIT_CLIENT_KEY = /* @__PURE__ */ Symbol(
|
|
329
|
+
"notify-kit-client"
|
|
330
|
+
);
|
|
331
|
+
function provideNotifyKitClient(config) {
|
|
332
|
+
const client = createNotifyKitClient(config);
|
|
333
|
+
provide(NOTIFY_KIT_CLIENT_KEY, client);
|
|
334
|
+
return client;
|
|
335
|
+
}
|
|
336
|
+
function useNotifyKitClient(config) {
|
|
337
|
+
let client;
|
|
338
|
+
if (config !== void 0) {
|
|
339
|
+
client = createNotifyKitClient(config);
|
|
340
|
+
} else {
|
|
341
|
+
const injectedClient = inject(NOTIFY_KIT_CLIENT_KEY);
|
|
342
|
+
if (injectedClient === void 0) {
|
|
343
|
+
throw new Error(
|
|
344
|
+
"No NotifyKit client available. Either provide config to useNotifyKitClient() or call provideNotifyKitClient() in a parent component."
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
client = injectedClient;
|
|
348
|
+
}
|
|
349
|
+
return {
|
|
350
|
+
client,
|
|
351
|
+
// Me
|
|
352
|
+
getMe: () => client.getMe(),
|
|
353
|
+
// Notifications
|
|
354
|
+
listNotifications: (params) => client.listNotifications(params),
|
|
355
|
+
getUnreadCount: () => client.getUnreadCount(),
|
|
356
|
+
markRead: (id) => client.markRead(id),
|
|
357
|
+
markAllRead: () => client.markAllRead(),
|
|
358
|
+
deleteNotification: (id) => client.deleteNotification(id),
|
|
359
|
+
// Groups
|
|
360
|
+
markGroupRead: (groupKey) => client.markGroupRead(groupKey),
|
|
361
|
+
getGroupNotifications: (groupKey, params) => client.getGroupNotifications(groupKey, params),
|
|
362
|
+
// Modals
|
|
363
|
+
listModals: () => client.listModals(),
|
|
364
|
+
ackModal: (id) => client.ackModal(id),
|
|
365
|
+
snoozeModal: (id, minutes) => client.snoozeModal(id, minutes),
|
|
366
|
+
// Preferences
|
|
367
|
+
getPreferences: () => client.getPreferences(),
|
|
368
|
+
updatePreferences: (preferences) => client.updatePreferences(preferences)
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
function useNotifyKitRealtime(options) {
|
|
372
|
+
const {
|
|
373
|
+
echo: echoOption,
|
|
374
|
+
client,
|
|
375
|
+
autoConnect = true,
|
|
376
|
+
onNotification,
|
|
377
|
+
onConnectionChange
|
|
378
|
+
} = options;
|
|
379
|
+
const store = useNotifyKitStore();
|
|
380
|
+
const connectionState = ref("disconnected");
|
|
381
|
+
const currentChannel = ref(null);
|
|
382
|
+
const isSubscribed = ref(false);
|
|
383
|
+
const isConnected = computed(() => connectionState.value === "connected");
|
|
384
|
+
function getEcho() {
|
|
385
|
+
return typeof echoOption === "function" ? echoOption() : echoOption;
|
|
386
|
+
}
|
|
387
|
+
function setConnectionState(state) {
|
|
388
|
+
connectionState.value = state;
|
|
389
|
+
store.setConnectionState(state);
|
|
390
|
+
onConnectionChange?.(state);
|
|
391
|
+
}
|
|
392
|
+
function handleNotification(payload) {
|
|
393
|
+
if (!isNotifyKitNotification(payload)) {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
store.addNotification(payload);
|
|
397
|
+
store.incrementUnreadCount();
|
|
398
|
+
if (isModalEnabled(payload)) {
|
|
399
|
+
store.enqueueModal(payload);
|
|
400
|
+
}
|
|
401
|
+
onNotification?.(payload);
|
|
402
|
+
}
|
|
403
|
+
async function connect() {
|
|
404
|
+
if (connectionState.value === "connected" || connectionState.value === "connecting") {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
setConnectionState("connecting");
|
|
408
|
+
try {
|
|
409
|
+
const meResponse = await client.getMe();
|
|
410
|
+
const channel = meResponse.broadcast_channel;
|
|
411
|
+
currentChannel.value = channel;
|
|
412
|
+
store.setBroadcastChannel(channel);
|
|
413
|
+
const echo = getEcho();
|
|
414
|
+
echo.private(channel).notification(handleNotification);
|
|
415
|
+
isSubscribed.value = true;
|
|
416
|
+
setConnectionState("connected");
|
|
417
|
+
} catch (error) {
|
|
418
|
+
setConnectionState("error");
|
|
419
|
+
throw error;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
function disconnect() {
|
|
423
|
+
if (currentChannel.value !== null && isSubscribed.value) {
|
|
424
|
+
const echo = getEcho();
|
|
425
|
+
echo.leave(currentChannel.value);
|
|
426
|
+
isSubscribed.value = false;
|
|
427
|
+
}
|
|
428
|
+
setConnectionState("disconnected");
|
|
429
|
+
}
|
|
430
|
+
async function reconnect() {
|
|
431
|
+
disconnect();
|
|
432
|
+
await connect();
|
|
433
|
+
}
|
|
434
|
+
onMounted(() => {
|
|
435
|
+
if (autoConnect) {
|
|
436
|
+
void connect();
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
onUnmounted(() => {
|
|
440
|
+
if (isConnected.value) {
|
|
441
|
+
disconnect();
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
return {
|
|
445
|
+
isConnected,
|
|
446
|
+
connectionState,
|
|
447
|
+
connect,
|
|
448
|
+
disconnect,
|
|
449
|
+
reconnect
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
var DEFAULT_CONFIG = {
|
|
453
|
+
disconnectThresholdMs: 5e3,
|
|
454
|
+
unreadCountIntervalMs: 3e4,
|
|
455
|
+
modalsIntervalMs: 12e4,
|
|
456
|
+
reconnectGracePeriodMs: 2e3
|
|
457
|
+
};
|
|
458
|
+
function useNotifyKitFallback(options) {
|
|
459
|
+
const { client, connectionState, config: userConfig, onModalsReceived, onUnreadCountReceived } = options;
|
|
460
|
+
const config = {
|
|
461
|
+
...DEFAULT_CONFIG,
|
|
462
|
+
...userConfig
|
|
463
|
+
};
|
|
464
|
+
const store = useNotifyKitStore();
|
|
465
|
+
const isPolling = ref(false);
|
|
466
|
+
const lastPollAt = ref(null);
|
|
467
|
+
let disconnectTimer = null;
|
|
468
|
+
let unreadCountInterval = null;
|
|
469
|
+
let modalsInterval = null;
|
|
470
|
+
let reconnectGraceTimer = null;
|
|
471
|
+
async function pollUnreadCount() {
|
|
472
|
+
try {
|
|
473
|
+
const response = await client.getUnreadCount();
|
|
474
|
+
store.setUnreadCount(response.count);
|
|
475
|
+
lastPollAt.value = Date.now();
|
|
476
|
+
onUnreadCountReceived?.(response.count);
|
|
477
|
+
} catch {
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
async function pollModals() {
|
|
481
|
+
try {
|
|
482
|
+
const modals = await client.listModals();
|
|
483
|
+
for (const modal of modals) {
|
|
484
|
+
store.enqueueModal(modal);
|
|
485
|
+
}
|
|
486
|
+
store.setLastModalsSync(Date.now());
|
|
487
|
+
lastPollAt.value = Date.now();
|
|
488
|
+
onModalsReceived?.(modals);
|
|
489
|
+
} catch {
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
function startPolling() {
|
|
493
|
+
if (isPolling.value) {
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
isPolling.value = true;
|
|
497
|
+
void pollUnreadCount();
|
|
498
|
+
void pollModals();
|
|
499
|
+
unreadCountInterval = setInterval(() => {
|
|
500
|
+
void pollUnreadCount();
|
|
501
|
+
}, config.unreadCountIntervalMs);
|
|
502
|
+
modalsInterval = setInterval(() => {
|
|
503
|
+
void pollModals();
|
|
504
|
+
}, config.modalsIntervalMs);
|
|
505
|
+
}
|
|
506
|
+
function stopPolling() {
|
|
507
|
+
if (!isPolling.value) {
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
isPolling.value = false;
|
|
511
|
+
if (unreadCountInterval !== null) {
|
|
512
|
+
clearInterval(unreadCountInterval);
|
|
513
|
+
unreadCountInterval = null;
|
|
514
|
+
}
|
|
515
|
+
if (modalsInterval !== null) {
|
|
516
|
+
clearInterval(modalsInterval);
|
|
517
|
+
modalsInterval = null;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
function clearAllTimers() {
|
|
521
|
+
if (disconnectTimer !== null) {
|
|
522
|
+
clearTimeout(disconnectTimer);
|
|
523
|
+
disconnectTimer = null;
|
|
524
|
+
}
|
|
525
|
+
if (reconnectGraceTimer !== null) {
|
|
526
|
+
clearTimeout(reconnectGraceTimer);
|
|
527
|
+
reconnectGraceTimer = null;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
function handleConnectionStateChange(state) {
|
|
531
|
+
if (state === "disconnected" || state === "error") {
|
|
532
|
+
if (reconnectGraceTimer !== null) {
|
|
533
|
+
clearTimeout(reconnectGraceTimer);
|
|
534
|
+
reconnectGraceTimer = null;
|
|
535
|
+
}
|
|
536
|
+
disconnectTimer ??= setTimeout(() => {
|
|
537
|
+
startPolling();
|
|
538
|
+
disconnectTimer = null;
|
|
539
|
+
}, config.disconnectThresholdMs);
|
|
540
|
+
} else if (state === "connected") {
|
|
541
|
+
if (disconnectTimer !== null) {
|
|
542
|
+
clearTimeout(disconnectTimer);
|
|
543
|
+
disconnectTimer = null;
|
|
544
|
+
}
|
|
545
|
+
if (isPolling.value) {
|
|
546
|
+
reconnectGraceTimer = setTimeout(() => {
|
|
547
|
+
stopPolling();
|
|
548
|
+
void pollUnreadCount();
|
|
549
|
+
void pollModals();
|
|
550
|
+
reconnectGraceTimer = null;
|
|
551
|
+
}, config.reconnectGracePeriodMs);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
watch(connectionState, handleConnectionStateChange, { immediate: true });
|
|
556
|
+
onUnmounted(() => {
|
|
557
|
+
clearAllTimers();
|
|
558
|
+
stopPolling();
|
|
559
|
+
});
|
|
560
|
+
return {
|
|
561
|
+
isPolling,
|
|
562
|
+
lastPollAt,
|
|
563
|
+
startPolling,
|
|
564
|
+
stopPolling,
|
|
565
|
+
pollUnreadCount,
|
|
566
|
+
pollModals
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// src/helpers/toastPolicy.ts
|
|
571
|
+
var DEFAULT_TOAST_CATEGORIES = {
|
|
572
|
+
workspace: true,
|
|
573
|
+
security: true,
|
|
574
|
+
orders: true,
|
|
575
|
+
social: false,
|
|
576
|
+
system: true,
|
|
577
|
+
marketing: true
|
|
578
|
+
};
|
|
579
|
+
var DEFAULT_SOUND_LEVELS = ["danger"];
|
|
580
|
+
var DEFAULT_SOUND_CATEGORIES = ["security"];
|
|
581
|
+
function isCriticalModal(notification) {
|
|
582
|
+
return notification.modal !== null && notification.modal.enabled && notification.modal.requires_ack;
|
|
583
|
+
}
|
|
584
|
+
function defaultShouldToast(notification, categorySettings) {
|
|
585
|
+
if (isCriticalModal(notification)) {
|
|
586
|
+
return false;
|
|
587
|
+
}
|
|
588
|
+
return categorySettings[notification.category];
|
|
589
|
+
}
|
|
590
|
+
function defaultShouldPlaySound(notification, soundLevels, soundCategories) {
|
|
591
|
+
if (soundLevels.includes(notification.level)) {
|
|
592
|
+
return true;
|
|
593
|
+
}
|
|
594
|
+
if (soundCategories.includes(notification.category)) {
|
|
595
|
+
return true;
|
|
596
|
+
}
|
|
597
|
+
return false;
|
|
598
|
+
}
|
|
599
|
+
var defaultToastPolicy = {
|
|
600
|
+
shouldToast(notification) {
|
|
601
|
+
return defaultShouldToast(notification, DEFAULT_TOAST_CATEGORIES);
|
|
602
|
+
},
|
|
603
|
+
shouldPlaySound(notification) {
|
|
604
|
+
return defaultShouldPlaySound(
|
|
605
|
+
notification,
|
|
606
|
+
DEFAULT_SOUND_LEVELS,
|
|
607
|
+
DEFAULT_SOUND_CATEGORIES
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
function createToastPolicy(config) {
|
|
612
|
+
const categorySettings = {
|
|
613
|
+
...DEFAULT_TOAST_CATEGORIES,
|
|
614
|
+
...config.toastCategories
|
|
615
|
+
};
|
|
616
|
+
const soundLevels = config.soundLevels ?? DEFAULT_SOUND_LEVELS;
|
|
617
|
+
const soundCategories = config.soundCategories ?? DEFAULT_SOUND_CATEGORIES;
|
|
618
|
+
return {
|
|
619
|
+
shouldToast(notification) {
|
|
620
|
+
if (config.shouldToast !== void 0) {
|
|
621
|
+
return config.shouldToast(notification);
|
|
622
|
+
}
|
|
623
|
+
return defaultShouldToast(notification, categorySettings);
|
|
624
|
+
},
|
|
625
|
+
shouldPlaySound(notification) {
|
|
626
|
+
if (config.shouldPlaySound !== void 0) {
|
|
627
|
+
return config.shouldPlaySound(notification);
|
|
628
|
+
}
|
|
629
|
+
return defaultShouldPlaySound(notification, soundLevels, soundCategories);
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// src/helpers/modalHelpers.ts
|
|
635
|
+
function getModalAriaAttributes(notification, ids) {
|
|
636
|
+
const ariaRole = notification.modal?.accessibility?.aria_role ?? "alertdialog";
|
|
637
|
+
return {
|
|
638
|
+
role: ariaRole,
|
|
639
|
+
"aria-modal": "true",
|
|
640
|
+
"aria-labelledby": ids.titleId,
|
|
641
|
+
"aria-describedby": ids.bodyId
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
function getEscapeKeyHandler(notification, callbacks) {
|
|
645
|
+
const behavior = notification.modal?.accessibility?.escape_key_behavior ?? "close";
|
|
646
|
+
if (behavior === "disabled") {
|
|
647
|
+
return null;
|
|
648
|
+
}
|
|
649
|
+
return (event) => {
|
|
650
|
+
if (event.key !== "Escape") return;
|
|
651
|
+
switch (behavior) {
|
|
652
|
+
case "close":
|
|
653
|
+
event.preventDefault();
|
|
654
|
+
callbacks.onClose();
|
|
655
|
+
break;
|
|
656
|
+
case "snooze": {
|
|
657
|
+
event.preventDefault();
|
|
658
|
+
const minSnooze = notification.modal?.snooze_options_minutes[0] ?? 15;
|
|
659
|
+
callbacks.onSnooze(minSnooze);
|
|
660
|
+
break;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
function createFocusTrap(modalElement) {
|
|
666
|
+
const focusableSelector = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
|
|
667
|
+
let previousFocus = null;
|
|
668
|
+
let trapHandler = null;
|
|
669
|
+
function trapFocus(event) {
|
|
670
|
+
if (event.key !== "Tab") return;
|
|
671
|
+
const focusable = modalElement.querySelectorAll(focusableSelector);
|
|
672
|
+
if (focusable.length === 0) return;
|
|
673
|
+
const first = focusable[0];
|
|
674
|
+
const last = focusable[focusable.length - 1];
|
|
675
|
+
if (first === void 0 || last === void 0) return;
|
|
676
|
+
if (event.shiftKey && document.activeElement === first) {
|
|
677
|
+
event.preventDefault();
|
|
678
|
+
last.focus();
|
|
679
|
+
} else if (!event.shiftKey && document.activeElement === last) {
|
|
680
|
+
event.preventDefault();
|
|
681
|
+
first.focus();
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
return {
|
|
685
|
+
activate() {
|
|
686
|
+
previousFocus = document.activeElement;
|
|
687
|
+
const focusable = modalElement.querySelectorAll(focusableSelector);
|
|
688
|
+
if (focusable.length > 0 && focusable[0] !== void 0) {
|
|
689
|
+
focusable[0].focus();
|
|
690
|
+
}
|
|
691
|
+
trapHandler = trapFocus;
|
|
692
|
+
modalElement.addEventListener("keydown", trapHandler);
|
|
693
|
+
},
|
|
694
|
+
deactivate() {
|
|
695
|
+
if (trapHandler !== null) {
|
|
696
|
+
modalElement.removeEventListener("keydown", trapHandler);
|
|
697
|
+
trapHandler = null;
|
|
698
|
+
}
|
|
699
|
+
if (previousFocus !== null) {
|
|
700
|
+
previousFocus.focus();
|
|
701
|
+
previousFocus = null;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
function getActionButtonAttributes(action, index) {
|
|
707
|
+
const attrs = {
|
|
708
|
+
type: "button",
|
|
709
|
+
"data-action-type": action.type,
|
|
710
|
+
"data-action-index": String(index),
|
|
711
|
+
"aria-label": action.label
|
|
712
|
+
};
|
|
713
|
+
if (action.primary === true) {
|
|
714
|
+
attrs["data-primary"] = true;
|
|
715
|
+
}
|
|
716
|
+
return attrs;
|
|
717
|
+
}
|
|
718
|
+
function isCriticalModal2(notification) {
|
|
719
|
+
return notification.modal?.enabled === true && notification.modal.requires_ack === true;
|
|
720
|
+
}
|
|
721
|
+
function shouldCloseOnBackdropClick(notification) {
|
|
722
|
+
return !isCriticalModal2(notification);
|
|
723
|
+
}
|
|
724
|
+
function getMinSnoozeDuration(notification) {
|
|
725
|
+
const options = notification.modal?.snooze_options_minutes;
|
|
726
|
+
if (options === void 0 || options.length === 0) {
|
|
727
|
+
return null;
|
|
728
|
+
}
|
|
729
|
+
return options[0] ?? null;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// src/index.ts
|
|
733
|
+
var VERSION = "0.1.0";
|
|
734
|
+
|
|
735
|
+
export { ENDPOINTS, NOTIFICATION_CATEGORIES, NOTIFICATION_LEVELS, NOTIFY_KIT_CLIENT_KEY, NotifyKitApiError, NotifyKitClient, VERSION, createFocusTrap, createNotifyKitClient, createToastPolicy, defaultToastPolicy, getActionButtonAttributes, getEscapeKeyHandler, getMinSnoozeDuration, getModalAriaAttributes, isChannelPreferences, isCriticalModal2 as isCriticalModal, isMeResponse, isModalEnabled, isModalSpec, isNotifyKitNotification, isPaginationMeta, isUnreadCountResponse, isValidCategory, isValidLevel, provideNotifyKitClient, shouldCloseOnBackdropClick, useNotifyKitClient, useNotifyKitFallback, useNotifyKitRealtime };
|
|
736
|
+
//# sourceMappingURL=index.js.map
|
|
737
|
+
//# sourceMappingURL=index.js.map
|