@notix-hub/sdk 0.3.1 → 0.3.3
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/capture.d.ts +2 -2
- package/dist/errors.d.ts +11 -4
- package/dist/index.cjs +150 -73
- package/dist/index.mjs +150 -73
- package/dist/notify.min.js +1 -1
- package/dist/notix.d.ts +1 -0
- package/dist/types.d.ts +12 -1
- package/package.json +40 -40
package/dist/capture.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { NotifyPayload } from './types';
|
|
1
|
+
import type { NotifyPayload, NotixResponse } from './types';
|
|
2
2
|
interface FormMeta {
|
|
3
3
|
title?: string;
|
|
4
4
|
fieldsCount: number;
|
|
5
5
|
}
|
|
6
6
|
type OnSubmit = (payload: NotifyPayload, meta: FormMeta) => void;
|
|
7
|
-
export declare function initFormCapture(onSubmit: OnSubmit, root: Element | Document, debug: boolean): void;
|
|
7
|
+
export declare function initFormCapture(onSubmit: OnSubmit, root: Element | Document, debug: boolean, onCapture?: (response: NotixResponse, payload: NotifyPayload) => void): void;
|
|
8
8
|
export declare function destroyFormCapture(): void;
|
|
9
9
|
export {};
|
package/dist/errors.d.ts
CHANGED
|
@@ -5,14 +5,21 @@ interface ErrorEntry {
|
|
|
5
5
|
col: number;
|
|
6
6
|
stack: string;
|
|
7
7
|
ts: string;
|
|
8
|
+
source: string;
|
|
9
|
+
}
|
|
10
|
+
interface ErrorBatch {
|
|
11
|
+
page: string;
|
|
12
|
+
errors: ErrorEntry[];
|
|
8
13
|
}
|
|
9
14
|
export declare class ErrorBatcher {
|
|
10
15
|
private sendFn;
|
|
11
|
-
private
|
|
12
|
-
|
|
13
|
-
constructor(sendFn: (errors: ErrorEntry[]) => void);
|
|
16
|
+
private currentPage;
|
|
17
|
+
constructor(sendFn: (batches: ErrorBatch[]) => void);
|
|
14
18
|
private bindGlobal;
|
|
15
|
-
private
|
|
19
|
+
private addError;
|
|
20
|
+
private load;
|
|
21
|
+
private save;
|
|
16
22
|
flush(): void;
|
|
23
|
+
clearAll(): void;
|
|
17
24
|
}
|
|
18
25
|
export {};
|
package/dist/index.cjs
CHANGED
|
@@ -13,6 +13,7 @@ function sendWebhook(endpoint, token, payload, timeout = DEFAULT_TIMEOUT) {
|
|
|
13
13
|
...(payload.type !== undefined && { notification_type: payload.type }),
|
|
14
14
|
...(payload.priority !== undefined && { priority: payload.priority }),
|
|
15
15
|
...(payload.tag !== undefined && { tag: payload.tag }),
|
|
16
|
+
...(payload.tags !== undefined && { tags: payload.tags }),
|
|
16
17
|
...(payload.payload !== undefined && { payload: payload.payload }),
|
|
17
18
|
};
|
|
18
19
|
const controller = new AbortController();
|
|
@@ -27,6 +28,7 @@ function sendWebhook(endpoint, token, payload, timeout = DEFAULT_TIMEOUT) {
|
|
|
27
28
|
},
|
|
28
29
|
body: JSON.stringify(body),
|
|
29
30
|
signal: controller.signal,
|
|
31
|
+
keepalive: true,
|
|
30
32
|
})
|
|
31
33
|
.then(async (res) => {
|
|
32
34
|
clearTimeout(timer);
|
|
@@ -73,17 +75,17 @@ function xhrSend(endpoint, token, body, timeout) {
|
|
|
73
75
|
|
|
74
76
|
const SUBMIT_DEBOUNCE_MS = 500;
|
|
75
77
|
const pendingForms = new WeakMap();
|
|
76
|
-
function initFormCapture(onSubmit, root, debug) {
|
|
77
|
-
const forms = root.querySelectorAll('form[data-notify]');
|
|
78
|
+
function initFormCapture(onSubmit, root, debug, onCapture) {
|
|
79
|
+
const forms = root.querySelectorAll('form[data-notix], form[data-notify]');
|
|
78
80
|
forms.forEach((form) => {
|
|
79
81
|
if (form.dataset.notixBound)
|
|
80
82
|
return;
|
|
81
83
|
form.dataset.notixBound = '1';
|
|
82
84
|
form.addEventListener('submit', (e) => {
|
|
85
|
+
e.preventDefault();
|
|
83
86
|
const now = Date.now();
|
|
84
87
|
const last = pendingForms.get(form) ?? 0;
|
|
85
88
|
if (now - last < SUBMIT_DEBOUNCE_MS) {
|
|
86
|
-
e.preventDefault();
|
|
87
89
|
return;
|
|
88
90
|
}
|
|
89
91
|
pendingForms.set(form, now);
|
|
@@ -92,10 +94,19 @@ function initFormCapture(onSubmit, root, debug) {
|
|
|
92
94
|
if (debug) {
|
|
93
95
|
console.log('[Notix] Form captured:', payload.title);
|
|
94
96
|
}
|
|
95
|
-
onSubmit(payload, { title: payload.title, fieldsCount: Object.keys(
|
|
97
|
+
onSubmit(payload, { title: payload.title, fieldsCount: payload.payload ? Object.keys(payload.payload).length : 0 });
|
|
98
|
+
// Per-form callback: data-notix-onsuccess="funcName"
|
|
99
|
+
const callbackName = attr(form, 'onsuccess');
|
|
100
|
+
if (callbackName) {
|
|
101
|
+
const fn = window[callbackName];
|
|
102
|
+
if (typeof fn === 'function') {
|
|
103
|
+
// The actual callback fires after notify() resolves — handled in notix.ts
|
|
104
|
+
form.dataset.notixCallback = callbackName;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (onCapture) ;
|
|
96
108
|
}
|
|
97
109
|
catch (err) {
|
|
98
|
-
// don't block form submission
|
|
99
110
|
if (debug) {
|
|
100
111
|
console.warn('[Notix] Form capture failed:', err);
|
|
101
112
|
}
|
|
@@ -106,64 +117,91 @@ function initFormCapture(onSubmit, root, debug) {
|
|
|
106
117
|
function destroyFormCapture() {
|
|
107
118
|
document.querySelectorAll('form[data-notix-bound]').forEach((form) => {
|
|
108
119
|
delete form.dataset.notixBound;
|
|
109
|
-
// listeners are removed by page navigation; for SPA use Notix.destroy() before unmount
|
|
110
120
|
});
|
|
111
121
|
}
|
|
122
|
+
function attr(form, name) {
|
|
123
|
+
const camel = name.charAt(0).toUpperCase() + name.slice(1);
|
|
124
|
+
return form.dataset[`notix${camel}`] ?? form.dataset[`notify${camel}`];
|
|
125
|
+
}
|
|
126
|
+
function elLabel(el) {
|
|
127
|
+
return el.getAttribute('data-notix-label') ?? el.getAttribute('data-notify-label') ?? el.name;
|
|
128
|
+
}
|
|
112
129
|
function extractPayload(form) {
|
|
113
|
-
const title = form
|
|
114
|
-
const
|
|
115
|
-
const tag = form
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
const
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
.filter(Boolean);
|
|
123
|
-
if (staticBody) {
|
|
124
|
-
return {
|
|
125
|
-
title,
|
|
126
|
-
body: staticBody,
|
|
127
|
-
...(type && { type }),
|
|
128
|
-
...(tag && { tag }),
|
|
129
|
-
...(priority && { priority: priority }),
|
|
130
|
-
};
|
|
130
|
+
const title = attr(form, 'title') ?? document.title;
|
|
131
|
+
const typeSlug = attr(form, 'type');
|
|
132
|
+
const tag = attr(form, 'tag');
|
|
133
|
+
const tagsStr = attr(form, 'tags');
|
|
134
|
+
const priority = attr(form, 'priority');
|
|
135
|
+
const bodyTemplate = attr(form, 'body');
|
|
136
|
+
const fieldsStr = attr(form, 'fields');
|
|
137
|
+
const allowedNames = new Set();
|
|
138
|
+
if (fieldsStr) {
|
|
139
|
+
fieldsStr.split(',').map(s => s.trim()).filter(Boolean).forEach(name => allowedNames.add(name));
|
|
131
140
|
}
|
|
141
|
+
if (bodyTemplate) {
|
|
142
|
+
const matches = bodyTemplate.matchAll(/\{(\w+)\}/g);
|
|
143
|
+
for (const m of matches) {
|
|
144
|
+
allowedNames.add(m[1]);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
let tags;
|
|
148
|
+
if (tag) {
|
|
149
|
+
tags = [tag];
|
|
150
|
+
}
|
|
151
|
+
if (tagsStr) {
|
|
152
|
+
try {
|
|
153
|
+
const parsed = JSON.parse(tagsStr);
|
|
154
|
+
tags = [...(tags || []), ...(Array.isArray(parsed) ? parsed : [parsed])];
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
tags = tags || [];
|
|
158
|
+
tags.push(tagsStr);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const fieldValues = {};
|
|
132
162
|
const elements = form.querySelectorAll('input[name], textarea[name], select[name]');
|
|
133
|
-
const fieldMap = new Map();
|
|
134
163
|
elements.forEach((el) => {
|
|
135
164
|
if (!el.name)
|
|
136
165
|
return;
|
|
166
|
+
if (!allowedNames.has(el.name))
|
|
167
|
+
return;
|
|
137
168
|
if (el.type === 'checkbox' || el.type === 'radio') {
|
|
138
169
|
if (!el.checked)
|
|
139
170
|
return;
|
|
140
171
|
}
|
|
141
|
-
|
|
142
|
-
return;
|
|
143
|
-
const isMarked = el.hasAttribute('data-notify-field');
|
|
144
|
-
if (specifiedFields || isMarked || (!specifiedFields && !template)) {
|
|
145
|
-
const label = el.getAttribute('data-notify-label') ?? el.name;
|
|
146
|
-
fieldMap.set(label, el.value || '');
|
|
147
|
-
}
|
|
172
|
+
fieldValues[el.name] = el.value || '';
|
|
148
173
|
});
|
|
149
174
|
let body;
|
|
150
|
-
if (
|
|
151
|
-
body =
|
|
152
|
-
if (fieldMap.has(key))
|
|
153
|
-
return fieldMap.get(key);
|
|
154
|
-
const el = form.elements.namedItem(key);
|
|
155
|
-
return el?.value || `{${key}}`;
|
|
156
|
-
});
|
|
175
|
+
if (bodyTemplate) {
|
|
176
|
+
body = bodyTemplate.replace(/\{(\w+)\}/g, (_, fieldName) => fieldValues[fieldName] ?? `{${fieldName}}`);
|
|
157
177
|
}
|
|
158
|
-
|
|
159
|
-
|
|
178
|
+
const payloadData = {};
|
|
179
|
+
elements.forEach((el) => {
|
|
180
|
+
if (!el.name)
|
|
181
|
+
return;
|
|
182
|
+
if (!allowedNames.has(el.name))
|
|
183
|
+
return;
|
|
184
|
+
if ((el.type === 'checkbox' || el.type === 'radio') && !el.checked)
|
|
185
|
+
return;
|
|
186
|
+
payloadData[elLabel(el)] = el.value || '';
|
|
187
|
+
});
|
|
188
|
+
let extraPayload = {};
|
|
189
|
+
const payloadJson = attr(form, 'payload');
|
|
190
|
+
if (payloadJson) {
|
|
191
|
+
try {
|
|
192
|
+
extraPayload = JSON.parse(payloadJson);
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
// ignore invalid JSON
|
|
196
|
+
}
|
|
160
197
|
}
|
|
161
198
|
return {
|
|
162
199
|
title,
|
|
163
200
|
...(body && { body }),
|
|
164
|
-
...(
|
|
165
|
-
...(
|
|
201
|
+
...(typeSlug && { type: typeSlug }),
|
|
202
|
+
...(tags && tags.length > 0 && { tags }),
|
|
166
203
|
...(priority && { priority: priority }),
|
|
204
|
+
payload: { ...payloadData, ...extraPayload },
|
|
167
205
|
};
|
|
168
206
|
}
|
|
169
207
|
|
|
@@ -224,44 +262,39 @@ class MetrikaTracker {
|
|
|
224
262
|
}
|
|
225
263
|
}
|
|
226
264
|
|
|
227
|
-
const
|
|
228
|
-
const
|
|
265
|
+
const STORAGE_KEY = 'notix_errors';
|
|
266
|
+
const MAX_ERRORS = 500;
|
|
229
267
|
class ErrorBatcher {
|
|
230
268
|
constructor(sendFn) {
|
|
231
269
|
this.sendFn = sendFn;
|
|
232
|
-
this.
|
|
233
|
-
this.timer = null;
|
|
270
|
+
this.currentPage = location.pathname;
|
|
234
271
|
this.bindGlobal();
|
|
235
272
|
}
|
|
236
273
|
bindGlobal() {
|
|
237
274
|
if (typeof window === 'undefined')
|
|
238
275
|
return;
|
|
239
|
-
const collect = (ev) => {
|
|
240
|
-
this.
|
|
276
|
+
const collect = (source) => (ev) => {
|
|
277
|
+
this.addError({
|
|
241
278
|
message: ev.message || 'Unknown error',
|
|
242
279
|
file: ev.filename || '',
|
|
243
280
|
line: ev.lineno || 0,
|
|
244
281
|
col: ev.colno || 0,
|
|
245
282
|
stack: ev.error?.stack || '',
|
|
246
283
|
ts: new Date().toISOString(),
|
|
284
|
+
source,
|
|
247
285
|
});
|
|
248
|
-
if (this.errors.length >= BATCH_SIZE)
|
|
249
|
-
this.flush();
|
|
250
|
-
this.startTimer();
|
|
251
286
|
};
|
|
252
|
-
window.addEventListener('error', collect);
|
|
287
|
+
window.addEventListener('error', collect('onerror'));
|
|
253
288
|
window.addEventListener('unhandledrejection', (ev) => {
|
|
254
|
-
this.
|
|
289
|
+
this.addError({
|
|
255
290
|
message: ev.reason?.message || String(ev.reason),
|
|
256
291
|
file: '',
|
|
257
292
|
line: 0,
|
|
258
293
|
col: 0,
|
|
259
294
|
stack: ev.reason?.stack || '',
|
|
260
295
|
ts: new Date().toISOString(),
|
|
296
|
+
source: 'unhandledrejection',
|
|
261
297
|
});
|
|
262
|
-
if (this.errors.length >= BATCH_SIZE)
|
|
263
|
-
this.flush();
|
|
264
|
-
this.startTimer();
|
|
265
298
|
});
|
|
266
299
|
window.addEventListener('beforeunload', () => this.flush());
|
|
267
300
|
document.addEventListener('visibilitychange', () => {
|
|
@@ -269,19 +302,49 @@ class ErrorBatcher {
|
|
|
269
302
|
this.flush();
|
|
270
303
|
});
|
|
271
304
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
305
|
+
addError(entry) {
|
|
306
|
+
const batches = this.load();
|
|
307
|
+
let batch = batches.find(b => b.page === this.currentPage);
|
|
308
|
+
if (!batch) {
|
|
309
|
+
batch = { page: this.currentPage, errors: [] };
|
|
310
|
+
batches.push(batch);
|
|
311
|
+
}
|
|
312
|
+
batch.errors.push(entry);
|
|
313
|
+
if (batch.errors.length > MAX_ERRORS) {
|
|
314
|
+
batch.errors = batch.errors.slice(-MAX_ERRORS);
|
|
315
|
+
}
|
|
316
|
+
this.save(batches);
|
|
317
|
+
}
|
|
318
|
+
load() {
|
|
319
|
+
try {
|
|
320
|
+
const raw = localStorage.getItem(STORAGE_KEY);
|
|
321
|
+
return raw ? JSON.parse(raw) : [];
|
|
322
|
+
}
|
|
323
|
+
catch {
|
|
324
|
+
return [];
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
save(batches) {
|
|
328
|
+
try {
|
|
329
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(batches));
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
// localStorage full — noop
|
|
333
|
+
}
|
|
276
334
|
}
|
|
277
335
|
flush() {
|
|
278
|
-
|
|
336
|
+
const batches = this.load();
|
|
337
|
+
if (batches.length === 0)
|
|
279
338
|
return;
|
|
280
|
-
this.sendFn(
|
|
281
|
-
this.
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
339
|
+
this.sendFn(batches);
|
|
340
|
+
this.clearAll();
|
|
341
|
+
}
|
|
342
|
+
clearAll() {
|
|
343
|
+
try {
|
|
344
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
// noop
|
|
285
348
|
}
|
|
286
349
|
}
|
|
287
350
|
}
|
|
@@ -298,7 +361,8 @@ class Notix {
|
|
|
298
361
|
this.timeout = config.timeout ?? 10000;
|
|
299
362
|
this.debug = config.debug ?? false;
|
|
300
363
|
this.metrika = createMetrikaTracker(config.metrika);
|
|
301
|
-
|
|
364
|
+
this.onCapture = config.onCapture;
|
|
365
|
+
if (config.autoCapture === true) {
|
|
302
366
|
this.capture();
|
|
303
367
|
}
|
|
304
368
|
if (config.metricEnabled) {
|
|
@@ -330,8 +394,19 @@ class Notix {
|
|
|
330
394
|
if (this.captureActive)
|
|
331
395
|
return;
|
|
332
396
|
initFormCapture((payload, meta) => {
|
|
333
|
-
this.notify(payload).then(() => {
|
|
397
|
+
this.notify(payload).then((response) => {
|
|
334
398
|
this.metrika?.trackFormCaptured(meta.title ?? payload.title, meta.fieldsCount);
|
|
399
|
+
// Per-form callback from data-notix-onsuccess
|
|
400
|
+
const form = (root instanceof Element ? root : document).querySelector('form[data-notix-bound]');
|
|
401
|
+
if (form instanceof HTMLFormElement && form.dataset.notixCallback) {
|
|
402
|
+
const fn = window[form.dataset.notixCallback];
|
|
403
|
+
if (typeof fn === 'function') {
|
|
404
|
+
fn(response);
|
|
405
|
+
}
|
|
406
|
+
delete form.dataset.notixCallback;
|
|
407
|
+
}
|
|
408
|
+
// Global callback from config
|
|
409
|
+
this.onCapture?.(response, payload);
|
|
335
410
|
}).catch(() => { });
|
|
336
411
|
}, root, this.debug);
|
|
337
412
|
this.captureActive = true;
|
|
@@ -361,11 +436,13 @@ class Notix {
|
|
|
361
436
|
enableErrorTracking() {
|
|
362
437
|
if (typeof window === 'undefined')
|
|
363
438
|
return;
|
|
364
|
-
|
|
439
|
+
const pageDomain = location.hostname;
|
|
440
|
+
new ErrorBatcher((batches) => {
|
|
441
|
+
const totalErrors = batches.reduce((sum, b) => sum + b.errors.length, 0);
|
|
365
442
|
this.notify({
|
|
366
|
-
title: `Ошибки на
|
|
443
|
+
title: `Ошибки на ${pageDomain}: ${totalErrors}`,
|
|
367
444
|
type: 'error_batch',
|
|
368
|
-
payload: {
|
|
445
|
+
payload: { batches },
|
|
369
446
|
}).catch(() => { });
|
|
370
447
|
});
|
|
371
448
|
this.log('Error tracking activated');
|
|
@@ -400,7 +477,7 @@ if (typeof document !== 'undefined') {
|
|
|
400
477
|
const token = thisScript.dataset.token;
|
|
401
478
|
if (token) {
|
|
402
479
|
const endpoint = thisScript.dataset.endpoint;
|
|
403
|
-
const autoCapture = thisScript.dataset.autoCapture
|
|
480
|
+
const autoCapture = thisScript.dataset.autoCapture === 'true';
|
|
404
481
|
const debug = thisScript.dataset.debug === 'true';
|
|
405
482
|
const timeout = thisScript.dataset.timeout ? parseInt(thisScript.dataset.timeout, 10) : undefined;
|
|
406
483
|
const ready = document.readyState !== 'loading';
|
package/dist/index.mjs
CHANGED
|
@@ -11,6 +11,7 @@ function sendWebhook(endpoint, token, payload, timeout = DEFAULT_TIMEOUT) {
|
|
|
11
11
|
...(payload.type !== undefined && { notification_type: payload.type }),
|
|
12
12
|
...(payload.priority !== undefined && { priority: payload.priority }),
|
|
13
13
|
...(payload.tag !== undefined && { tag: payload.tag }),
|
|
14
|
+
...(payload.tags !== undefined && { tags: payload.tags }),
|
|
14
15
|
...(payload.payload !== undefined && { payload: payload.payload }),
|
|
15
16
|
};
|
|
16
17
|
const controller = new AbortController();
|
|
@@ -25,6 +26,7 @@ function sendWebhook(endpoint, token, payload, timeout = DEFAULT_TIMEOUT) {
|
|
|
25
26
|
},
|
|
26
27
|
body: JSON.stringify(body),
|
|
27
28
|
signal: controller.signal,
|
|
29
|
+
keepalive: true,
|
|
28
30
|
})
|
|
29
31
|
.then(async (res) => {
|
|
30
32
|
clearTimeout(timer);
|
|
@@ -71,17 +73,17 @@ function xhrSend(endpoint, token, body, timeout) {
|
|
|
71
73
|
|
|
72
74
|
const SUBMIT_DEBOUNCE_MS = 500;
|
|
73
75
|
const pendingForms = new WeakMap();
|
|
74
|
-
function initFormCapture(onSubmit, root, debug) {
|
|
75
|
-
const forms = root.querySelectorAll('form[data-notify]');
|
|
76
|
+
function initFormCapture(onSubmit, root, debug, onCapture) {
|
|
77
|
+
const forms = root.querySelectorAll('form[data-notix], form[data-notify]');
|
|
76
78
|
forms.forEach((form) => {
|
|
77
79
|
if (form.dataset.notixBound)
|
|
78
80
|
return;
|
|
79
81
|
form.dataset.notixBound = '1';
|
|
80
82
|
form.addEventListener('submit', (e) => {
|
|
83
|
+
e.preventDefault();
|
|
81
84
|
const now = Date.now();
|
|
82
85
|
const last = pendingForms.get(form) ?? 0;
|
|
83
86
|
if (now - last < SUBMIT_DEBOUNCE_MS) {
|
|
84
|
-
e.preventDefault();
|
|
85
87
|
return;
|
|
86
88
|
}
|
|
87
89
|
pendingForms.set(form, now);
|
|
@@ -90,10 +92,19 @@ function initFormCapture(onSubmit, root, debug) {
|
|
|
90
92
|
if (debug) {
|
|
91
93
|
console.log('[Notix] Form captured:', payload.title);
|
|
92
94
|
}
|
|
93
|
-
onSubmit(payload, { title: payload.title, fieldsCount: Object.keys(
|
|
95
|
+
onSubmit(payload, { title: payload.title, fieldsCount: payload.payload ? Object.keys(payload.payload).length : 0 });
|
|
96
|
+
// Per-form callback: data-notix-onsuccess="funcName"
|
|
97
|
+
const callbackName = attr(form, 'onsuccess');
|
|
98
|
+
if (callbackName) {
|
|
99
|
+
const fn = window[callbackName];
|
|
100
|
+
if (typeof fn === 'function') {
|
|
101
|
+
// The actual callback fires after notify() resolves — handled in notix.ts
|
|
102
|
+
form.dataset.notixCallback = callbackName;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (onCapture) ;
|
|
94
106
|
}
|
|
95
107
|
catch (err) {
|
|
96
|
-
// don't block form submission
|
|
97
108
|
if (debug) {
|
|
98
109
|
console.warn('[Notix] Form capture failed:', err);
|
|
99
110
|
}
|
|
@@ -104,64 +115,91 @@ function initFormCapture(onSubmit, root, debug) {
|
|
|
104
115
|
function destroyFormCapture() {
|
|
105
116
|
document.querySelectorAll('form[data-notix-bound]').forEach((form) => {
|
|
106
117
|
delete form.dataset.notixBound;
|
|
107
|
-
// listeners are removed by page navigation; for SPA use Notix.destroy() before unmount
|
|
108
118
|
});
|
|
109
119
|
}
|
|
120
|
+
function attr(form, name) {
|
|
121
|
+
const camel = name.charAt(0).toUpperCase() + name.slice(1);
|
|
122
|
+
return form.dataset[`notix${camel}`] ?? form.dataset[`notify${camel}`];
|
|
123
|
+
}
|
|
124
|
+
function elLabel(el) {
|
|
125
|
+
return el.getAttribute('data-notix-label') ?? el.getAttribute('data-notify-label') ?? el.name;
|
|
126
|
+
}
|
|
110
127
|
function extractPayload(form) {
|
|
111
|
-
const title = form
|
|
112
|
-
const
|
|
113
|
-
const tag = form
|
|
114
|
-
const
|
|
115
|
-
const
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
.filter(Boolean);
|
|
121
|
-
if (staticBody) {
|
|
122
|
-
return {
|
|
123
|
-
title,
|
|
124
|
-
body: staticBody,
|
|
125
|
-
...(type && { type }),
|
|
126
|
-
...(tag && { tag }),
|
|
127
|
-
...(priority && { priority: priority }),
|
|
128
|
-
};
|
|
128
|
+
const title = attr(form, 'title') ?? document.title;
|
|
129
|
+
const typeSlug = attr(form, 'type');
|
|
130
|
+
const tag = attr(form, 'tag');
|
|
131
|
+
const tagsStr = attr(form, 'tags');
|
|
132
|
+
const priority = attr(form, 'priority');
|
|
133
|
+
const bodyTemplate = attr(form, 'body');
|
|
134
|
+
const fieldsStr = attr(form, 'fields');
|
|
135
|
+
const allowedNames = new Set();
|
|
136
|
+
if (fieldsStr) {
|
|
137
|
+
fieldsStr.split(',').map(s => s.trim()).filter(Boolean).forEach(name => allowedNames.add(name));
|
|
129
138
|
}
|
|
139
|
+
if (bodyTemplate) {
|
|
140
|
+
const matches = bodyTemplate.matchAll(/\{(\w+)\}/g);
|
|
141
|
+
for (const m of matches) {
|
|
142
|
+
allowedNames.add(m[1]);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
let tags;
|
|
146
|
+
if (tag) {
|
|
147
|
+
tags = [tag];
|
|
148
|
+
}
|
|
149
|
+
if (tagsStr) {
|
|
150
|
+
try {
|
|
151
|
+
const parsed = JSON.parse(tagsStr);
|
|
152
|
+
tags = [...(tags || []), ...(Array.isArray(parsed) ? parsed : [parsed])];
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
tags = tags || [];
|
|
156
|
+
tags.push(tagsStr);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const fieldValues = {};
|
|
130
160
|
const elements = form.querySelectorAll('input[name], textarea[name], select[name]');
|
|
131
|
-
const fieldMap = new Map();
|
|
132
161
|
elements.forEach((el) => {
|
|
133
162
|
if (!el.name)
|
|
134
163
|
return;
|
|
164
|
+
if (!allowedNames.has(el.name))
|
|
165
|
+
return;
|
|
135
166
|
if (el.type === 'checkbox' || el.type === 'radio') {
|
|
136
167
|
if (!el.checked)
|
|
137
168
|
return;
|
|
138
169
|
}
|
|
139
|
-
|
|
140
|
-
return;
|
|
141
|
-
const isMarked = el.hasAttribute('data-notify-field');
|
|
142
|
-
if (specifiedFields || isMarked || (!specifiedFields && !template)) {
|
|
143
|
-
const label = el.getAttribute('data-notify-label') ?? el.name;
|
|
144
|
-
fieldMap.set(label, el.value || '');
|
|
145
|
-
}
|
|
170
|
+
fieldValues[el.name] = el.value || '';
|
|
146
171
|
});
|
|
147
172
|
let body;
|
|
148
|
-
if (
|
|
149
|
-
body =
|
|
150
|
-
if (fieldMap.has(key))
|
|
151
|
-
return fieldMap.get(key);
|
|
152
|
-
const el = form.elements.namedItem(key);
|
|
153
|
-
return el?.value || `{${key}}`;
|
|
154
|
-
});
|
|
173
|
+
if (bodyTemplate) {
|
|
174
|
+
body = bodyTemplate.replace(/\{(\w+)\}/g, (_, fieldName) => fieldValues[fieldName] ?? `{${fieldName}}`);
|
|
155
175
|
}
|
|
156
|
-
|
|
157
|
-
|
|
176
|
+
const payloadData = {};
|
|
177
|
+
elements.forEach((el) => {
|
|
178
|
+
if (!el.name)
|
|
179
|
+
return;
|
|
180
|
+
if (!allowedNames.has(el.name))
|
|
181
|
+
return;
|
|
182
|
+
if ((el.type === 'checkbox' || el.type === 'radio') && !el.checked)
|
|
183
|
+
return;
|
|
184
|
+
payloadData[elLabel(el)] = el.value || '';
|
|
185
|
+
});
|
|
186
|
+
let extraPayload = {};
|
|
187
|
+
const payloadJson = attr(form, 'payload');
|
|
188
|
+
if (payloadJson) {
|
|
189
|
+
try {
|
|
190
|
+
extraPayload = JSON.parse(payloadJson);
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
// ignore invalid JSON
|
|
194
|
+
}
|
|
158
195
|
}
|
|
159
196
|
return {
|
|
160
197
|
title,
|
|
161
198
|
...(body && { body }),
|
|
162
|
-
...(
|
|
163
|
-
...(
|
|
199
|
+
...(typeSlug && { type: typeSlug }),
|
|
200
|
+
...(tags && tags.length > 0 && { tags }),
|
|
164
201
|
...(priority && { priority: priority }),
|
|
202
|
+
payload: { ...payloadData, ...extraPayload },
|
|
165
203
|
};
|
|
166
204
|
}
|
|
167
205
|
|
|
@@ -222,44 +260,39 @@ class MetrikaTracker {
|
|
|
222
260
|
}
|
|
223
261
|
}
|
|
224
262
|
|
|
225
|
-
const
|
|
226
|
-
const
|
|
263
|
+
const STORAGE_KEY = 'notix_errors';
|
|
264
|
+
const MAX_ERRORS = 500;
|
|
227
265
|
class ErrorBatcher {
|
|
228
266
|
constructor(sendFn) {
|
|
229
267
|
this.sendFn = sendFn;
|
|
230
|
-
this.
|
|
231
|
-
this.timer = null;
|
|
268
|
+
this.currentPage = location.pathname;
|
|
232
269
|
this.bindGlobal();
|
|
233
270
|
}
|
|
234
271
|
bindGlobal() {
|
|
235
272
|
if (typeof window === 'undefined')
|
|
236
273
|
return;
|
|
237
|
-
const collect = (ev) => {
|
|
238
|
-
this.
|
|
274
|
+
const collect = (source) => (ev) => {
|
|
275
|
+
this.addError({
|
|
239
276
|
message: ev.message || 'Unknown error',
|
|
240
277
|
file: ev.filename || '',
|
|
241
278
|
line: ev.lineno || 0,
|
|
242
279
|
col: ev.colno || 0,
|
|
243
280
|
stack: ev.error?.stack || '',
|
|
244
281
|
ts: new Date().toISOString(),
|
|
282
|
+
source,
|
|
245
283
|
});
|
|
246
|
-
if (this.errors.length >= BATCH_SIZE)
|
|
247
|
-
this.flush();
|
|
248
|
-
this.startTimer();
|
|
249
284
|
};
|
|
250
|
-
window.addEventListener('error', collect);
|
|
285
|
+
window.addEventListener('error', collect('onerror'));
|
|
251
286
|
window.addEventListener('unhandledrejection', (ev) => {
|
|
252
|
-
this.
|
|
287
|
+
this.addError({
|
|
253
288
|
message: ev.reason?.message || String(ev.reason),
|
|
254
289
|
file: '',
|
|
255
290
|
line: 0,
|
|
256
291
|
col: 0,
|
|
257
292
|
stack: ev.reason?.stack || '',
|
|
258
293
|
ts: new Date().toISOString(),
|
|
294
|
+
source: 'unhandledrejection',
|
|
259
295
|
});
|
|
260
|
-
if (this.errors.length >= BATCH_SIZE)
|
|
261
|
-
this.flush();
|
|
262
|
-
this.startTimer();
|
|
263
296
|
});
|
|
264
297
|
window.addEventListener('beforeunload', () => this.flush());
|
|
265
298
|
document.addEventListener('visibilitychange', () => {
|
|
@@ -267,19 +300,49 @@ class ErrorBatcher {
|
|
|
267
300
|
this.flush();
|
|
268
301
|
});
|
|
269
302
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
303
|
+
addError(entry) {
|
|
304
|
+
const batches = this.load();
|
|
305
|
+
let batch = batches.find(b => b.page === this.currentPage);
|
|
306
|
+
if (!batch) {
|
|
307
|
+
batch = { page: this.currentPage, errors: [] };
|
|
308
|
+
batches.push(batch);
|
|
309
|
+
}
|
|
310
|
+
batch.errors.push(entry);
|
|
311
|
+
if (batch.errors.length > MAX_ERRORS) {
|
|
312
|
+
batch.errors = batch.errors.slice(-MAX_ERRORS);
|
|
313
|
+
}
|
|
314
|
+
this.save(batches);
|
|
315
|
+
}
|
|
316
|
+
load() {
|
|
317
|
+
try {
|
|
318
|
+
const raw = localStorage.getItem(STORAGE_KEY);
|
|
319
|
+
return raw ? JSON.parse(raw) : [];
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
return [];
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
save(batches) {
|
|
326
|
+
try {
|
|
327
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(batches));
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
// localStorage full — noop
|
|
331
|
+
}
|
|
274
332
|
}
|
|
275
333
|
flush() {
|
|
276
|
-
|
|
334
|
+
const batches = this.load();
|
|
335
|
+
if (batches.length === 0)
|
|
277
336
|
return;
|
|
278
|
-
this.sendFn(
|
|
279
|
-
this.
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
337
|
+
this.sendFn(batches);
|
|
338
|
+
this.clearAll();
|
|
339
|
+
}
|
|
340
|
+
clearAll() {
|
|
341
|
+
try {
|
|
342
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
343
|
+
}
|
|
344
|
+
catch {
|
|
345
|
+
// noop
|
|
283
346
|
}
|
|
284
347
|
}
|
|
285
348
|
}
|
|
@@ -296,7 +359,8 @@ class Notix {
|
|
|
296
359
|
this.timeout = config.timeout ?? 10000;
|
|
297
360
|
this.debug = config.debug ?? false;
|
|
298
361
|
this.metrika = createMetrikaTracker(config.metrika);
|
|
299
|
-
|
|
362
|
+
this.onCapture = config.onCapture;
|
|
363
|
+
if (config.autoCapture === true) {
|
|
300
364
|
this.capture();
|
|
301
365
|
}
|
|
302
366
|
if (config.metricEnabled) {
|
|
@@ -328,8 +392,19 @@ class Notix {
|
|
|
328
392
|
if (this.captureActive)
|
|
329
393
|
return;
|
|
330
394
|
initFormCapture((payload, meta) => {
|
|
331
|
-
this.notify(payload).then(() => {
|
|
395
|
+
this.notify(payload).then((response) => {
|
|
332
396
|
this.metrika?.trackFormCaptured(meta.title ?? payload.title, meta.fieldsCount);
|
|
397
|
+
// Per-form callback from data-notix-onsuccess
|
|
398
|
+
const form = (root instanceof Element ? root : document).querySelector('form[data-notix-bound]');
|
|
399
|
+
if (form instanceof HTMLFormElement && form.dataset.notixCallback) {
|
|
400
|
+
const fn = window[form.dataset.notixCallback];
|
|
401
|
+
if (typeof fn === 'function') {
|
|
402
|
+
fn(response);
|
|
403
|
+
}
|
|
404
|
+
delete form.dataset.notixCallback;
|
|
405
|
+
}
|
|
406
|
+
// Global callback from config
|
|
407
|
+
this.onCapture?.(response, payload);
|
|
333
408
|
}).catch(() => { });
|
|
334
409
|
}, root, this.debug);
|
|
335
410
|
this.captureActive = true;
|
|
@@ -359,11 +434,13 @@ class Notix {
|
|
|
359
434
|
enableErrorTracking() {
|
|
360
435
|
if (typeof window === 'undefined')
|
|
361
436
|
return;
|
|
362
|
-
|
|
437
|
+
const pageDomain = location.hostname;
|
|
438
|
+
new ErrorBatcher((batches) => {
|
|
439
|
+
const totalErrors = batches.reduce((sum, b) => sum + b.errors.length, 0);
|
|
363
440
|
this.notify({
|
|
364
|
-
title: `Ошибки на
|
|
441
|
+
title: `Ошибки на ${pageDomain}: ${totalErrors}`,
|
|
365
442
|
type: 'error_batch',
|
|
366
|
-
payload: {
|
|
443
|
+
payload: { batches },
|
|
367
444
|
}).catch(() => { });
|
|
368
445
|
});
|
|
369
446
|
this.log('Error tracking activated');
|
|
@@ -398,7 +475,7 @@ if (typeof document !== 'undefined') {
|
|
|
398
475
|
const token = thisScript.dataset.token;
|
|
399
476
|
if (token) {
|
|
400
477
|
const endpoint = thisScript.dataset.endpoint;
|
|
401
|
-
const autoCapture = thisScript.dataset.autoCapture
|
|
478
|
+
const autoCapture = thisScript.dataset.autoCapture === 'true';
|
|
402
479
|
const debug = thisScript.dataset.debug === 'true';
|
|
403
480
|
const timeout = thisScript.dataset.timeout ? parseInt(thisScript.dataset.timeout, 10) : undefined;
|
|
404
481
|
const ready = document.readyState !== 'loading';
|
package/dist/notify.min.js
CHANGED
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
* @version <%= pkg.version %>
|
|
4
4
|
* @license MIT
|
|
5
5
|
*/
|
|
6
|
-
var Notix=function(t){"use strict";function e(t,e,r
|
|
6
|
+
var Notix=function(t){"use strict";function e(t,e,o,r=1e4){const n={title:o.title,...void 0!==o.body&&{body:o.body},...void 0!==o.type&&{notification_type:o.type},...void 0!==o.priority&&{priority:o.priority},...void 0!==o.tag&&{tag:o.tag},...void 0!==o.tags&&{tags:o.tags},...void 0!==o.payload&&{payload:o.payload}},a=new AbortController,i=setTimeout(()=>a.abort(),r);return"undefined"!=typeof fetch?fetch(t,{method:"POST",headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(n),signal:a.signal,keepalive:!0}).then(async t=>{clearTimeout(i);const e=await t.json();if(!t.ok)throw new Error(e.error||e.message||`HTTP ${t.status}`);return e}).catch(t=>{throw clearTimeout(i),t}):function(t,e,o,r){return new Promise((n,a)=>{const i=new XMLHttpRequest;i.open("POST",t,!0),i.setRequestHeader("Authorization",`Bearer ${e}`),i.setRequestHeader("Content-Type","application/json"),i.setRequestHeader("Accept","application/json"),i.timeout=r,i.onload=()=>{try{const t=JSON.parse(i.responseText);i.status>=200&&i.status<300?n(t):a(new Error(t.error||t.message||`HTTP ${i.status}`))}catch{a(new Error("Invalid response"))}},i.onerror=()=>a(new Error("Network error")),i.ontimeout=()=>a(new Error("Request timeout")),i.send(JSON.stringify(o))})}(t,e,n,r)}const o=new WeakMap;function r(t,e,r,a){e.querySelectorAll("form[data-notix], form[data-notify]").forEach(e=>{e.dataset.notixBound||(e.dataset.notixBound="1",e.addEventListener("submit",a=>{a.preventDefault();const i=Date.now();if(!(i-(o.get(e)??0)<500)){o.set(e,i);try{const o=function(t){const e=n(t,"title")??document.title,o=n(t,"type"),r=n(t,"tag"),a=n(t,"tags"),i=n(t,"priority"),s=n(t,"body"),c=n(t,"fields"),d=new Set;c&&c.split(",").map(t=>t.trim()).filter(Boolean).forEach(t=>d.add(t));if(s){const t=s.matchAll(/\{(\w+)\}/g);for(const e of t)d.add(e[1])}let l;r&&(l=[r]);if(a)try{const t=JSON.parse(a);l=[...l||[],...Array.isArray(t)?t:[t]]}catch{l=l||[],l.push(a)}const u={},h=t.querySelectorAll("input[name], textarea[name], select[name]");let p;h.forEach(t=>{t.name&&d.has(t.name)&&("checkbox"!==t.type&&"radio"!==t.type||t.checked)&&(u[t.name]=t.value||"")}),s&&(p=s.replace(/\{(\w+)\}/g,(t,e)=>u[e]??`{${e}}`));const f={};h.forEach(t=>{t.name&&d.has(t.name)&&("checkbox"!==t.type&&"radio"!==t.type||t.checked)&&(f[function(t){return t.getAttribute("data-notix-label")??t.getAttribute("data-notify-label")??t.name}(t)]=t.value||"")});let m={};const g=n(t,"payload");if(g)try{m=JSON.parse(g)}catch{}return{title:e,...p&&{body:p},...o&&{type:o},...l&&l.length>0&&{tags:l},...i&&{priority:i},payload:{...f,...m}}}(e);r&&console.log("[Notix] Form captured:",o.title),t(o,{title:o.title,fieldsCount:o.payload?Object.keys(o.payload).length:0});const a=n(e,"onsuccess");if(a){"function"==typeof window[a]&&(e.dataset.notixCallback=a)}}catch(t){r&&console.warn("[Notix] Form capture failed:",t)}}}))})}function n(t,e){const o=e.charAt(0).toUpperCase()+e.slice(1);return t.dataset[`notix${o}`]??t.dataset[`notify${o}`]}function a(t){if(!1===t?.enabled)return null;const e="undefined"!=typeof window&&"function"==typeof window.ym;if(!e&&!t?.enabled)return null;let o=t?.counterId;return!o&&e&&(o=function(){for(const t of Object.keys(window))if(t.startsWith("yaCounter")){const e=parseInt(t.replace("yaCounter",""),10);if(!isNaN(e))return e}return}()),o?new i(o,t?.prefix??"notix"):null}class i{constructor(t,e){this.counterId=t,this.prefix=e}trackSent(t){this.reachGoal(`${this.prefix}_sent`,{id:t.id,message:t.message})}trackError(t){this.reachGoal(`${this.prefix}_error`,{error:t.message})}trackFormCaptured(t,e){this.reachGoal(`${this.prefix}_form_captured`,{title:t,fields:e})}trackGoal(t){this.reachGoal(t,{})}reachGoal(t,e){"function"==typeof window.ym&&window.ym(this.counterId,"reachGoal",t,e)}}const s="notix_errors";class c{constructor(t){this.sendFn=t,this.currentPage=location.pathname,this.bindGlobal()}bindGlobal(){if("undefined"==typeof window)return;window.addEventListener("error",(t=>e=>{this.addError({message:e.message||"Unknown error",file:e.filename||"",line:e.lineno||0,col:e.colno||0,stack:e.error?.stack||"",ts:(new Date).toISOString(),source:t})})("onerror")),window.addEventListener("unhandledrejection",t=>{this.addError({message:t.reason?.message||String(t.reason),file:"",line:0,col:0,stack:t.reason?.stack||"",ts:(new Date).toISOString(),source:"unhandledrejection"})}),window.addEventListener("beforeunload",()=>this.flush()),document.addEventListener("visibilitychange",()=>{"hidden"===document.visibilityState&&this.flush()})}addError(t){const e=this.load();let o=e.find(t=>t.page===this.currentPage);o||(o={page:this.currentPage,errors:[]},e.push(o)),o.errors.push(t),o.errors.length>500&&(o.errors=o.errors.slice(-500)),this.save(e)}load(){try{const t=localStorage.getItem(s);return t?JSON.parse(t):[]}catch{return[]}}save(t){try{localStorage.setItem(s,JSON.stringify(t))}catch{}}flush(){const t=this.load();0!==t.length&&(this.sendFn(t),this.clearAll())}clearAll(){try{localStorage.removeItem(s)}catch{}}}class d{constructor(t){if(this.captureActive=!1,!t.token||!t.token.startsWith("ntx_"))throw new Error('Notix: token must start with "ntx_"');this.token=t.token,this.endpoint=t.endpoint??"https://notix-hub.ru/api/v1/webhook",this.timeout=t.timeout??1e4,this.debug=t.debug??!1,this.metrika=a(t.metrika),this.onCapture=t.onCapture,!0===t.autoCapture&&this.capture(),t.metricEnabled&&this.sendPageview(),t.errorTrackingEnabled&&this.enableErrorTracking()}async notify(t){if(!t.title)throw new Error("Notix: title is required");this.log("Sending notification:",t.title);try{const o=await e(this.endpoint,this.token,t,this.timeout);return this.log("Notification sent:",o.id),this.metrika?.trackSent(o),o}catch(t){const e=t instanceof Error?t:new Error(String(t));throw this.log("Error sending notification:",e.message),this.metrika?.trackError(e),e}}capture(t=document){this.captureActive||(r((e,o)=>{this.notify(e).then(r=>{this.metrika?.trackFormCaptured(o.title??e.title,o.fieldsCount);const n=(t instanceof Element?t:document).querySelector("form[data-notix-bound]");if(n instanceof HTMLFormElement&&n.dataset.notixCallback){const t=window[n.dataset.notixCallback];"function"==typeof t&&t(r),delete n.dataset.notixCallback}this.onCapture?.(r,e)}).catch(()=>{})},t,this.debug),this.captureActive=!0,this.log("Form capture activated"))}destroy(){document.querySelectorAll("form[data-notix-bound]").forEach(t=>{delete t.dataset.notixBound}),this.captureActive=!1,this.log("Notix destroyed")}sendPageview(t,e){const o={title:"Посетитель на сайте",type:"metric",payload:{event_type:"pageview",visitor_id:u(),page:t||location.pathname,referrer:e||document.referrer||void 0,value:1}};this.notify(o).catch(()=>{}),this.log("Pageview sent:",t||location.pathname)}enableErrorTracking(){if("undefined"==typeof window)return;const t=location.hostname;new c(e=>{const o=e.reduce((t,e)=>t+e.errors.length,0);this.notify({title:`Ошибки на ${t}: ${o}`,type:"error_batch",payload:{batches:e}}).catch(()=>{})}),this.log("Error tracking activated")}static getVisitorId(){return u()}log(...t){this.debug&&console.log("[Notix]",...t)}}const l="notix_vid";function u(){try{let t=localStorage.getItem(l);return t||(t=crypto.randomUUID(),localStorage.setItem(l,t)),t}catch{return"unknown"}}if("undefined"!=typeof document){const t=document.currentScript;if(t){const e=t.dataset.token;if(e){const o=t.dataset.endpoint,r="true"===t.dataset.autoCapture,n="true"===t.dataset.debug,a=t.dataset.timeout?parseInt(t.dataset.timeout,10):void 0,i=()=>{const i="true"===t.dataset.metric,s="true"===t.dataset.errors,c=new d({token:e,...o&&{endpoint:o},autoCapture:r,debug:n,metricEnabled:i,errorTrackingEnabled:s,...a&&{timeout:a}});window.notix=c};"loading"!==document.readyState?i():document.addEventListener("DOMContentLoaded",i)}else"true"===t.dataset.debug&&console.warn("[Notix] data-token attribute is required on script tag")}}return t.Notix=d,t}({});
|
package/dist/notix.d.ts
CHANGED
package/dist/types.d.ts
CHANGED
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
export type NotificationPriority = 'low' | 'normal' | 'high' | 'urgent';
|
|
2
|
+
export interface TagObject {
|
|
3
|
+
name: string;
|
|
4
|
+
color?: string;
|
|
5
|
+
}
|
|
2
6
|
export interface NotifyPayload {
|
|
3
7
|
title: string;
|
|
4
8
|
body?: string;
|
|
5
9
|
type?: string;
|
|
6
10
|
tag?: string;
|
|
11
|
+
tags?: Array<string | TagObject>;
|
|
7
12
|
priority?: NotificationPriority;
|
|
8
13
|
payload?: Record<string, unknown>;
|
|
9
14
|
}
|
|
10
|
-
export interface NotifyRequestBody
|
|
15
|
+
export interface NotifyRequestBody {
|
|
16
|
+
title: string;
|
|
17
|
+
body?: string;
|
|
11
18
|
notification_type?: string;
|
|
12
19
|
tag?: string;
|
|
20
|
+
tags?: Array<string | TagObject>;
|
|
21
|
+
priority?: string;
|
|
22
|
+
payload?: Record<string, unknown>;
|
|
13
23
|
}
|
|
14
24
|
export interface NotixResponse {
|
|
15
25
|
message: string;
|
|
@@ -29,4 +39,5 @@ export interface NotixConfig {
|
|
|
29
39
|
metrika?: MetrikaConfig;
|
|
30
40
|
timeout?: number;
|
|
31
41
|
debug?: boolean;
|
|
42
|
+
onCapture?: (response: NotixResponse, payload: NotifyPayload) => void;
|
|
32
43
|
}
|
package/package.json
CHANGED
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@notix-hub/sdk",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"description": "JavaScript SDK for Notix (Нотикс) — notification aggregation service",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "./dist/index.cjs",
|
|
7
|
-
"module": "./dist/index.mjs",
|
|
8
|
-
"types": "./dist/index.d.ts",
|
|
9
|
-
"exports": {
|
|
10
|
-
".": {
|
|
11
|
-
"import": "./dist/index.mjs",
|
|
12
|
-
"require": "./dist/index.cjs",
|
|
13
|
-
"types": "./dist/index.d.ts"
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
"files": [
|
|
17
|
-
"dist"
|
|
18
|
-
],
|
|
19
|
-
"scripts": {
|
|
20
|
-
"build": "rollup -c",
|
|
21
|
-
"dev": "rollup -c -w",
|
|
22
|
-
"typecheck": "tsc --noEmit",
|
|
23
|
-
"prepare": "npm run build",
|
|
24
|
-
"prepublishOnly": "npm run build"
|
|
25
|
-
},
|
|
26
|
-
"keywords": [
|
|
27
|
-
"notix",
|
|
28
|
-
"notifications",
|
|
29
|
-
"webhook",
|
|
30
|
-
"notify"
|
|
31
|
-
],
|
|
32
|
-
"license": "MIT",
|
|
33
|
-
"devDependencies": {
|
|
34
|
-
"@rollup/plugin-typescript": "^12.0.0",
|
|
35
|
-
"@rollup/plugin-terser": "^0.4.0",
|
|
36
|
-
"rollup": "^4.0.0",
|
|
37
|
-
"tslib": "^2.0.0",
|
|
38
|
-
"typescript": "^5.0.0"
|
|
39
|
-
}
|
|
40
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@notix-hub/sdk",
|
|
3
|
+
"version": "0.3.3",
|
|
4
|
+
"description": "JavaScript SDK for Notix (Нотикс) — notification aggregation service",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.mjs",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.cjs",
|
|
13
|
+
"types": "./dist/index.d.ts"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "rollup -c",
|
|
21
|
+
"dev": "rollup -c -w",
|
|
22
|
+
"typecheck": "tsc --noEmit",
|
|
23
|
+
"prepare": "npm run build",
|
|
24
|
+
"prepublishOnly": "npm run build"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"notix",
|
|
28
|
+
"notifications",
|
|
29
|
+
"webhook",
|
|
30
|
+
"notify"
|
|
31
|
+
],
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@rollup/plugin-typescript": "^12.0.0",
|
|
35
|
+
"@rollup/plugin-terser": "^0.4.0",
|
|
36
|
+
"rollup": "^4.0.0",
|
|
37
|
+
"tslib": "^2.0.0",
|
|
38
|
+
"typescript": "^5.0.0"
|
|
39
|
+
}
|
|
40
|
+
}
|