@notix-hub/sdk 0.2.1 → 0.3.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/errors.d.ts +18 -0
- package/dist/index.cjs +108 -0
- package/dist/index.mjs +108 -0
- package/dist/notify.min.js +1 -1
- package/dist/notix.d.ts +3 -0
- package/package.json +1 -1
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface ErrorEntry {
|
|
2
|
+
message: string;
|
|
3
|
+
file: string;
|
|
4
|
+
line: number;
|
|
5
|
+
col: number;
|
|
6
|
+
stack: string;
|
|
7
|
+
ts: string;
|
|
8
|
+
}
|
|
9
|
+
export declare class ErrorBatcher {
|
|
10
|
+
private sendFn;
|
|
11
|
+
private errors;
|
|
12
|
+
private timer;
|
|
13
|
+
constructor(sendFn: (errors: ErrorEntry[]) => void);
|
|
14
|
+
private bindGlobal;
|
|
15
|
+
private startTimer;
|
|
16
|
+
flush(): void;
|
|
17
|
+
}
|
|
18
|
+
export {};
|
package/dist/index.cjs
CHANGED
|
@@ -224,6 +224,68 @@ class MetrikaTracker {
|
|
|
224
224
|
}
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
+
const BATCH_SIZE = 10;
|
|
228
|
+
const FLUSH_INTERVAL = 60000;
|
|
229
|
+
class ErrorBatcher {
|
|
230
|
+
constructor(sendFn) {
|
|
231
|
+
this.sendFn = sendFn;
|
|
232
|
+
this.errors = [];
|
|
233
|
+
this.timer = null;
|
|
234
|
+
this.bindGlobal();
|
|
235
|
+
}
|
|
236
|
+
bindGlobal() {
|
|
237
|
+
if (typeof window === 'undefined')
|
|
238
|
+
return;
|
|
239
|
+
const collect = (ev) => {
|
|
240
|
+
this.errors.push({
|
|
241
|
+
message: ev.message || 'Unknown error',
|
|
242
|
+
file: ev.filename || '',
|
|
243
|
+
line: ev.lineno || 0,
|
|
244
|
+
col: ev.colno || 0,
|
|
245
|
+
stack: ev.error?.stack || '',
|
|
246
|
+
ts: new Date().toISOString(),
|
|
247
|
+
});
|
|
248
|
+
if (this.errors.length >= BATCH_SIZE)
|
|
249
|
+
this.flush();
|
|
250
|
+
this.startTimer();
|
|
251
|
+
};
|
|
252
|
+
window.addEventListener('error', collect);
|
|
253
|
+
window.addEventListener('unhandledrejection', (ev) => {
|
|
254
|
+
this.errors.push({
|
|
255
|
+
message: ev.reason?.message || String(ev.reason),
|
|
256
|
+
file: '',
|
|
257
|
+
line: 0,
|
|
258
|
+
col: 0,
|
|
259
|
+
stack: ev.reason?.stack || '',
|
|
260
|
+
ts: new Date().toISOString(),
|
|
261
|
+
});
|
|
262
|
+
if (this.errors.length >= BATCH_SIZE)
|
|
263
|
+
this.flush();
|
|
264
|
+
this.startTimer();
|
|
265
|
+
});
|
|
266
|
+
window.addEventListener('beforeunload', () => this.flush());
|
|
267
|
+
document.addEventListener('visibilitychange', () => {
|
|
268
|
+
if (document.visibilityState === 'hidden')
|
|
269
|
+
this.flush();
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
startTimer() {
|
|
273
|
+
if (this.timer)
|
|
274
|
+
return;
|
|
275
|
+
this.timer = setTimeout(() => this.flush(), FLUSH_INTERVAL);
|
|
276
|
+
}
|
|
277
|
+
flush() {
|
|
278
|
+
if (this.errors.length === 0)
|
|
279
|
+
return;
|
|
280
|
+
this.sendFn([...this.errors]);
|
|
281
|
+
this.errors = [];
|
|
282
|
+
if (this.timer) {
|
|
283
|
+
clearTimeout(this.timer);
|
|
284
|
+
this.timer = null;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
227
289
|
const DEFAULT_ENDPOINT = 'https://notix-hub.ru/api/v1/webhook';
|
|
228
290
|
class Notix {
|
|
229
291
|
constructor(config) {
|
|
@@ -239,6 +301,7 @@ class Notix {
|
|
|
239
301
|
if (config.autoCapture !== false) {
|
|
240
302
|
this.capture();
|
|
241
303
|
}
|
|
304
|
+
this.trackErrors();
|
|
242
305
|
}
|
|
243
306
|
async notify(payload) {
|
|
244
307
|
if (!payload.title) {
|
|
@@ -274,12 +337,57 @@ class Notix {
|
|
|
274
337
|
this.captureActive = false;
|
|
275
338
|
this.log('Notix destroyed');
|
|
276
339
|
}
|
|
340
|
+
sendPageview(page, referrer) {
|
|
341
|
+
const visitorId = getOrCreateVisitorId();
|
|
342
|
+
const payload = {
|
|
343
|
+
title: 'Посетитель на сайте',
|
|
344
|
+
type: 'metric',
|
|
345
|
+
payload: {
|
|
346
|
+
event_type: 'pageview',
|
|
347
|
+
visitor_id: visitorId,
|
|
348
|
+
page: page || location.pathname,
|
|
349
|
+
referrer: referrer || document.referrer || undefined,
|
|
350
|
+
value: 1,
|
|
351
|
+
},
|
|
352
|
+
};
|
|
353
|
+
this.notify(payload).catch(() => { });
|
|
354
|
+
this.log('Pageview sent:', page || location.pathname);
|
|
355
|
+
}
|
|
356
|
+
trackErrors() {
|
|
357
|
+
if (typeof window === 'undefined')
|
|
358
|
+
return;
|
|
359
|
+
new ErrorBatcher((errors) => {
|
|
360
|
+
this.notify({
|
|
361
|
+
title: `Ошибки на странице (${errors.length})`,
|
|
362
|
+
type: 'error_batch',
|
|
363
|
+
payload: { errors },
|
|
364
|
+
}).catch(() => { });
|
|
365
|
+
});
|
|
366
|
+
this.log('Error tracking activated');
|
|
367
|
+
}
|
|
368
|
+
static getVisitorId() {
|
|
369
|
+
return getOrCreateVisitorId();
|
|
370
|
+
}
|
|
277
371
|
log(...args) {
|
|
278
372
|
if (this.debug) {
|
|
279
373
|
console.log('[Notix]', ...args);
|
|
280
374
|
}
|
|
281
375
|
}
|
|
282
376
|
}
|
|
377
|
+
const VISITOR_KEY = 'notix_vid';
|
|
378
|
+
function getOrCreateVisitorId() {
|
|
379
|
+
try {
|
|
380
|
+
let vid = localStorage.getItem(VISITOR_KEY);
|
|
381
|
+
if (!vid) {
|
|
382
|
+
vid = crypto.randomUUID();
|
|
383
|
+
localStorage.setItem(VISITOR_KEY, vid);
|
|
384
|
+
}
|
|
385
|
+
return vid;
|
|
386
|
+
}
|
|
387
|
+
catch {
|
|
388
|
+
return 'unknown';
|
|
389
|
+
}
|
|
390
|
+
}
|
|
283
391
|
|
|
284
392
|
if (typeof document !== 'undefined') {
|
|
285
393
|
const thisScript = document.currentScript;
|
package/dist/index.mjs
CHANGED
|
@@ -222,6 +222,68 @@ class MetrikaTracker {
|
|
|
222
222
|
}
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
+
const BATCH_SIZE = 10;
|
|
226
|
+
const FLUSH_INTERVAL = 60000;
|
|
227
|
+
class ErrorBatcher {
|
|
228
|
+
constructor(sendFn) {
|
|
229
|
+
this.sendFn = sendFn;
|
|
230
|
+
this.errors = [];
|
|
231
|
+
this.timer = null;
|
|
232
|
+
this.bindGlobal();
|
|
233
|
+
}
|
|
234
|
+
bindGlobal() {
|
|
235
|
+
if (typeof window === 'undefined')
|
|
236
|
+
return;
|
|
237
|
+
const collect = (ev) => {
|
|
238
|
+
this.errors.push({
|
|
239
|
+
message: ev.message || 'Unknown error',
|
|
240
|
+
file: ev.filename || '',
|
|
241
|
+
line: ev.lineno || 0,
|
|
242
|
+
col: ev.colno || 0,
|
|
243
|
+
stack: ev.error?.stack || '',
|
|
244
|
+
ts: new Date().toISOString(),
|
|
245
|
+
});
|
|
246
|
+
if (this.errors.length >= BATCH_SIZE)
|
|
247
|
+
this.flush();
|
|
248
|
+
this.startTimer();
|
|
249
|
+
};
|
|
250
|
+
window.addEventListener('error', collect);
|
|
251
|
+
window.addEventListener('unhandledrejection', (ev) => {
|
|
252
|
+
this.errors.push({
|
|
253
|
+
message: ev.reason?.message || String(ev.reason),
|
|
254
|
+
file: '',
|
|
255
|
+
line: 0,
|
|
256
|
+
col: 0,
|
|
257
|
+
stack: ev.reason?.stack || '',
|
|
258
|
+
ts: new Date().toISOString(),
|
|
259
|
+
});
|
|
260
|
+
if (this.errors.length >= BATCH_SIZE)
|
|
261
|
+
this.flush();
|
|
262
|
+
this.startTimer();
|
|
263
|
+
});
|
|
264
|
+
window.addEventListener('beforeunload', () => this.flush());
|
|
265
|
+
document.addEventListener('visibilitychange', () => {
|
|
266
|
+
if (document.visibilityState === 'hidden')
|
|
267
|
+
this.flush();
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
startTimer() {
|
|
271
|
+
if (this.timer)
|
|
272
|
+
return;
|
|
273
|
+
this.timer = setTimeout(() => this.flush(), FLUSH_INTERVAL);
|
|
274
|
+
}
|
|
275
|
+
flush() {
|
|
276
|
+
if (this.errors.length === 0)
|
|
277
|
+
return;
|
|
278
|
+
this.sendFn([...this.errors]);
|
|
279
|
+
this.errors = [];
|
|
280
|
+
if (this.timer) {
|
|
281
|
+
clearTimeout(this.timer);
|
|
282
|
+
this.timer = null;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
225
287
|
const DEFAULT_ENDPOINT = 'https://notix-hub.ru/api/v1/webhook';
|
|
226
288
|
class Notix {
|
|
227
289
|
constructor(config) {
|
|
@@ -237,6 +299,7 @@ class Notix {
|
|
|
237
299
|
if (config.autoCapture !== false) {
|
|
238
300
|
this.capture();
|
|
239
301
|
}
|
|
302
|
+
this.trackErrors();
|
|
240
303
|
}
|
|
241
304
|
async notify(payload) {
|
|
242
305
|
if (!payload.title) {
|
|
@@ -272,12 +335,57 @@ class Notix {
|
|
|
272
335
|
this.captureActive = false;
|
|
273
336
|
this.log('Notix destroyed');
|
|
274
337
|
}
|
|
338
|
+
sendPageview(page, referrer) {
|
|
339
|
+
const visitorId = getOrCreateVisitorId();
|
|
340
|
+
const payload = {
|
|
341
|
+
title: 'Посетитель на сайте',
|
|
342
|
+
type: 'metric',
|
|
343
|
+
payload: {
|
|
344
|
+
event_type: 'pageview',
|
|
345
|
+
visitor_id: visitorId,
|
|
346
|
+
page: page || location.pathname,
|
|
347
|
+
referrer: referrer || document.referrer || undefined,
|
|
348
|
+
value: 1,
|
|
349
|
+
},
|
|
350
|
+
};
|
|
351
|
+
this.notify(payload).catch(() => { });
|
|
352
|
+
this.log('Pageview sent:', page || location.pathname);
|
|
353
|
+
}
|
|
354
|
+
trackErrors() {
|
|
355
|
+
if (typeof window === 'undefined')
|
|
356
|
+
return;
|
|
357
|
+
new ErrorBatcher((errors) => {
|
|
358
|
+
this.notify({
|
|
359
|
+
title: `Ошибки на странице (${errors.length})`,
|
|
360
|
+
type: 'error_batch',
|
|
361
|
+
payload: { errors },
|
|
362
|
+
}).catch(() => { });
|
|
363
|
+
});
|
|
364
|
+
this.log('Error tracking activated');
|
|
365
|
+
}
|
|
366
|
+
static getVisitorId() {
|
|
367
|
+
return getOrCreateVisitorId();
|
|
368
|
+
}
|
|
275
369
|
log(...args) {
|
|
276
370
|
if (this.debug) {
|
|
277
371
|
console.log('[Notix]', ...args);
|
|
278
372
|
}
|
|
279
373
|
}
|
|
280
374
|
}
|
|
375
|
+
const VISITOR_KEY = 'notix_vid';
|
|
376
|
+
function getOrCreateVisitorId() {
|
|
377
|
+
try {
|
|
378
|
+
let vid = localStorage.getItem(VISITOR_KEY);
|
|
379
|
+
if (!vid) {
|
|
380
|
+
vid = crypto.randomUUID();
|
|
381
|
+
localStorage.setItem(VISITOR_KEY, vid);
|
|
382
|
+
}
|
|
383
|
+
return vid;
|
|
384
|
+
}
|
|
385
|
+
catch {
|
|
386
|
+
return 'unknown';
|
|
387
|
+
}
|
|
388
|
+
}
|
|
281
389
|
|
|
282
390
|
if (typeof document !== 'undefined') {
|
|
283
391
|
const thisScript = document.currentScript;
|
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,
|
|
6
|
+
var Notix=function(t){"use strict";function e(t,e,i,r=1e4){const o={title:i.title,...void 0!==i.body&&{body:i.body},...void 0!==i.type&&{notification_type:i.type},...void 0!==i.priority&&{priority:i.priority},...void 0!==i.tag&&{tag:i.tag},...void 0!==i.payload&&{payload:i.payload}},n=new AbortController,a=setTimeout(()=>n.abort(),r);return"undefined"!=typeof fetch?fetch(t,{method:"POST",headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(o),signal:n.signal}).then(async t=>{clearTimeout(a);const e=await t.json();if(!t.ok)throw new Error(e.error||e.message||`HTTP ${t.status}`);return e}).catch(t=>{throw clearTimeout(a),t}):function(t,e,i,r){return new Promise((o,n)=>{const a=new XMLHttpRequest;a.open("POST",t,!0),a.setRequestHeader("Authorization",`Bearer ${e}`),a.setRequestHeader("Content-Type","application/json"),a.setRequestHeader("Accept","application/json"),a.timeout=r,a.onload=()=>{try{const t=JSON.parse(a.responseText);a.status>=200&&a.status<300?o(t):n(new Error(t.error||t.message||`HTTP ${a.status}`))}catch{n(new Error("Invalid response"))}},a.onerror=()=>n(new Error("Network error")),a.ontimeout=()=>n(new Error("Request timeout")),a.send(JSON.stringify(i))})}(t,e,o,r)}const i=new WeakMap;function r(t,e,r){e.querySelectorAll("form[data-notify]").forEach(e=>{e.dataset.notixBound||(e.dataset.notixBound="1",e.addEventListener("submit",o=>{const n=Date.now();if(n-(i.get(e)??0)<500)o.preventDefault();else{i.set(e,n);try{const i=function(t){const e=t.dataset.notifyTitle??t.dataset.notify_title??document.title,i=t.dataset.notifyType??t.dataset.notify_type,r=t.dataset.notifyTag??t.dataset.notify_tag,o=t.dataset.notifyPriority??t.dataset.notify_priority,n=t.dataset.notifyBody??t.dataset.notify_body,a=t.dataset.notifyTemplate??t.dataset.notify_template,s=(t.dataset.notifyFields??t.dataset.notify_fields)?.split(",").map(t=>t.trim()).filter(Boolean);if(n)return{title:e,body:n,...i&&{type:i},...r&&{tag:r},...o&&{priority:o}};const d=t.querySelectorAll("input[name], textarea[name], select[name]"),c=new Map;let u;d.forEach(t=>{if(!t.name)return;if(("checkbox"===t.type||"radio"===t.type)&&!t.checked)return;if(s&&!s.includes(t.name))return;const e=t.hasAttribute("data-notify-field");if(s||e||!s&&!a){const e=t.getAttribute("data-notify-label")??t.name;c.set(e,t.value||"")}}),a?u=a.replace(/\{(\w+)\}/g,(e,i)=>{if(c.has(i))return c.get(i);const r=t.elements.namedItem(i);return r?.value||`{${i}}`}):c.size>0&&(u=Array.from(c,([t,e])=>`${t}: ${e}`).join("\n"));return{title:e,...u&&{body:u},...i&&{type:i},...r&&{tag:r},...o&&{priority:o}}}(e);r&&console.log("[Notix] Form captured:",i.title),t(i,{title:i.title,fieldsCount:Object.keys(e.elements).length})}catch(t){r&&console.warn("[Notix] Form capture failed:",t)}}}))})}function o(t){if(!1===t?.enabled)return null;const e="undefined"!=typeof window&&"function"==typeof window.ym;if(!e&&!t?.enabled)return null;let i=t?.counterId;return!i&&e&&(i=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}()),i?new n(i,t?.prefix??"notix"):null}class n{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)}}class a{constructor(t){this.sendFn=t,this.errors=[],this.timer=null,this.bindGlobal()}bindGlobal(){if("undefined"==typeof window)return;window.addEventListener("error",t=>{this.errors.push({message:t.message||"Unknown error",file:t.filename||"",line:t.lineno||0,col:t.colno||0,stack:t.error?.stack||"",ts:(new Date).toISOString()}),this.errors.length>=10&&this.flush(),this.startTimer()}),window.addEventListener("unhandledrejection",t=>{this.errors.push({message:t.reason?.message||String(t.reason),file:"",line:0,col:0,stack:t.reason?.stack||"",ts:(new Date).toISOString()}),this.errors.length>=10&&this.flush(),this.startTimer()}),window.addEventListener("beforeunload",()=>this.flush()),document.addEventListener("visibilitychange",()=>{"hidden"===document.visibilityState&&this.flush()})}startTimer(){this.timer||(this.timer=setTimeout(()=>this.flush(),6e4))}flush(){0!==this.errors.length&&(this.sendFn([...this.errors]),this.errors=[],this.timer&&(clearTimeout(this.timer),this.timer=null))}}class s{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=o(t.metrika),!1!==t.autoCapture&&this.capture(),this.trackErrors()}async notify(t){if(!t.title)throw new Error("Notix: title is required");this.log("Sending notification:",t.title);try{const i=await e(this.endpoint,this.token,t,this.timeout);return this.log("Notification sent:",i.id),this.metrika?.trackSent(i),i}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((t,e)=>{this.notify(t).then(()=>{this.metrika?.trackFormCaptured(e.title??t.title,e.fieldsCount)}).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 i={title:"Посетитель на сайте",type:"metric",payload:{event_type:"pageview",visitor_id:c(),page:t||location.pathname,referrer:e||document.referrer||void 0,value:1}};this.notify(i).catch(()=>{}),this.log("Pageview sent:",t||location.pathname)}trackErrors(){"undefined"!=typeof window&&(new a(t=>{this.notify({title:`Ошибки на странице (${t.length})`,type:"error_batch",payload:{errors:t}}).catch(()=>{})}),this.log("Error tracking activated"))}static getVisitorId(){return c()}log(...t){this.debug&&console.log("[Notix]",...t)}}const d="notix_vid";function c(){try{let t=localStorage.getItem(d);return t||(t=crypto.randomUUID(),localStorage.setItem(d,t)),t}catch{return"unknown"}}if("undefined"!=typeof document){const t=document.currentScript;if(t){const e=t.dataset.token;if(e){const i=t.dataset.endpoint,r="false"!==t.dataset.autoCapture,o="true"===t.dataset.debug,n=t.dataset.timeout?parseInt(t.dataset.timeout,10):void 0,a=()=>{const t=new s({token:e,...i&&{endpoint:i},autoCapture:r,debug:o,...n&&{timeout:n}});window.notix=t};"loading"!==document.readyState?a():document.addEventListener("DOMContentLoaded",a)}else"true"===t.dataset.debug&&console.warn("[Notix] data-token attribute is required on script tag")}}return t.Notix=s,t}({});
|
package/dist/notix.d.ts
CHANGED