@notix-hub/sdk 0.3.2 → 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/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 errors;
12
- private timer;
13
- constructor(sendFn: (errors: ErrorEntry[]) => void);
16
+ private currentPage;
17
+ constructor(sendFn: (batches: ErrorBatch[]) => void);
14
18
  private bindGlobal;
15
- private startTimer;
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
@@ -262,44 +262,39 @@ class MetrikaTracker {
262
262
  }
263
263
  }
264
264
 
265
- const BATCH_SIZE = 10;
266
- const FLUSH_INTERVAL = 60000;
265
+ const STORAGE_KEY = 'notix_errors';
266
+ const MAX_ERRORS = 500;
267
267
  class ErrorBatcher {
268
268
  constructor(sendFn) {
269
269
  this.sendFn = sendFn;
270
- this.errors = [];
271
- this.timer = null;
270
+ this.currentPage = location.pathname;
272
271
  this.bindGlobal();
273
272
  }
274
273
  bindGlobal() {
275
274
  if (typeof window === 'undefined')
276
275
  return;
277
- const collect = (ev) => {
278
- this.errors.push({
276
+ const collect = (source) => (ev) => {
277
+ this.addError({
279
278
  message: ev.message || 'Unknown error',
280
279
  file: ev.filename || '',
281
280
  line: ev.lineno || 0,
282
281
  col: ev.colno || 0,
283
282
  stack: ev.error?.stack || '',
284
283
  ts: new Date().toISOString(),
284
+ source,
285
285
  });
286
- if (this.errors.length >= BATCH_SIZE)
287
- this.flush();
288
- this.startTimer();
289
286
  };
290
- window.addEventListener('error', collect);
287
+ window.addEventListener('error', collect('onerror'));
291
288
  window.addEventListener('unhandledrejection', (ev) => {
292
- this.errors.push({
289
+ this.addError({
293
290
  message: ev.reason?.message || String(ev.reason),
294
291
  file: '',
295
292
  line: 0,
296
293
  col: 0,
297
294
  stack: ev.reason?.stack || '',
298
295
  ts: new Date().toISOString(),
296
+ source: 'unhandledrejection',
299
297
  });
300
- if (this.errors.length >= BATCH_SIZE)
301
- this.flush();
302
- this.startTimer();
303
298
  });
304
299
  window.addEventListener('beforeunload', () => this.flush());
305
300
  document.addEventListener('visibilitychange', () => {
@@ -307,19 +302,49 @@ class ErrorBatcher {
307
302
  this.flush();
308
303
  });
309
304
  }
310
- startTimer() {
311
- if (this.timer)
312
- return;
313
- this.timer = setTimeout(() => this.flush(), FLUSH_INTERVAL);
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
+ }
314
334
  }
315
335
  flush() {
316
- if (this.errors.length === 0)
336
+ const batches = this.load();
337
+ if (batches.length === 0)
317
338
  return;
318
- this.sendFn([...this.errors]);
319
- this.errors = [];
320
- if (this.timer) {
321
- clearTimeout(this.timer);
322
- this.timer = null;
339
+ this.sendFn(batches);
340
+ this.clearAll();
341
+ }
342
+ clearAll() {
343
+ try {
344
+ localStorage.removeItem(STORAGE_KEY);
345
+ }
346
+ catch {
347
+ // noop
323
348
  }
324
349
  }
325
350
  }
@@ -411,11 +436,13 @@ class Notix {
411
436
  enableErrorTracking() {
412
437
  if (typeof window === 'undefined')
413
438
  return;
414
- new ErrorBatcher((errors) => {
439
+ const pageDomain = location.hostname;
440
+ new ErrorBatcher((batches) => {
441
+ const totalErrors = batches.reduce((sum, b) => sum + b.errors.length, 0);
415
442
  this.notify({
416
- title: `Ошибки на странице (${errors.length})`,
443
+ title: `Ошибки на ${pageDomain}: ${totalErrors}`,
417
444
  type: 'error_batch',
418
- payload: { errors },
445
+ payload: { batches },
419
446
  }).catch(() => { });
420
447
  });
421
448
  this.log('Error tracking activated');
package/dist/index.mjs CHANGED
@@ -260,44 +260,39 @@ class MetrikaTracker {
260
260
  }
261
261
  }
262
262
 
263
- const BATCH_SIZE = 10;
264
- const FLUSH_INTERVAL = 60000;
263
+ const STORAGE_KEY = 'notix_errors';
264
+ const MAX_ERRORS = 500;
265
265
  class ErrorBatcher {
266
266
  constructor(sendFn) {
267
267
  this.sendFn = sendFn;
268
- this.errors = [];
269
- this.timer = null;
268
+ this.currentPage = location.pathname;
270
269
  this.bindGlobal();
271
270
  }
272
271
  bindGlobal() {
273
272
  if (typeof window === 'undefined')
274
273
  return;
275
- const collect = (ev) => {
276
- this.errors.push({
274
+ const collect = (source) => (ev) => {
275
+ this.addError({
277
276
  message: ev.message || 'Unknown error',
278
277
  file: ev.filename || '',
279
278
  line: ev.lineno || 0,
280
279
  col: ev.colno || 0,
281
280
  stack: ev.error?.stack || '',
282
281
  ts: new Date().toISOString(),
282
+ source,
283
283
  });
284
- if (this.errors.length >= BATCH_SIZE)
285
- this.flush();
286
- this.startTimer();
287
284
  };
288
- window.addEventListener('error', collect);
285
+ window.addEventListener('error', collect('onerror'));
289
286
  window.addEventListener('unhandledrejection', (ev) => {
290
- this.errors.push({
287
+ this.addError({
291
288
  message: ev.reason?.message || String(ev.reason),
292
289
  file: '',
293
290
  line: 0,
294
291
  col: 0,
295
292
  stack: ev.reason?.stack || '',
296
293
  ts: new Date().toISOString(),
294
+ source: 'unhandledrejection',
297
295
  });
298
- if (this.errors.length >= BATCH_SIZE)
299
- this.flush();
300
- this.startTimer();
301
296
  });
302
297
  window.addEventListener('beforeunload', () => this.flush());
303
298
  document.addEventListener('visibilitychange', () => {
@@ -305,19 +300,49 @@ class ErrorBatcher {
305
300
  this.flush();
306
301
  });
307
302
  }
308
- startTimer() {
309
- if (this.timer)
310
- return;
311
- this.timer = setTimeout(() => this.flush(), FLUSH_INTERVAL);
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
+ }
312
332
  }
313
333
  flush() {
314
- if (this.errors.length === 0)
334
+ const batches = this.load();
335
+ if (batches.length === 0)
315
336
  return;
316
- this.sendFn([...this.errors]);
317
- this.errors = [];
318
- if (this.timer) {
319
- clearTimeout(this.timer);
320
- this.timer = null;
337
+ this.sendFn(batches);
338
+ this.clearAll();
339
+ }
340
+ clearAll() {
341
+ try {
342
+ localStorage.removeItem(STORAGE_KEY);
343
+ }
344
+ catch {
345
+ // noop
321
346
  }
322
347
  }
323
348
  }
@@ -409,11 +434,13 @@ class Notix {
409
434
  enableErrorTracking() {
410
435
  if (typeof window === 'undefined')
411
436
  return;
412
- new ErrorBatcher((errors) => {
437
+ const pageDomain = location.hostname;
438
+ new ErrorBatcher((batches) => {
439
+ const totalErrors = batches.reduce((sum, b) => sum + b.errors.length, 0);
413
440
  this.notify({
414
- title: `Ошибки на странице (${errors.length})`,
441
+ title: `Ошибки на ${pageDomain}: ${totalErrors}`,
415
442
  type: 'error_batch',
416
- payload: { errors },
443
+ payload: { batches },
417
444
  }).catch(() => { });
418
445
  });
419
446
  this.log('Error tracking activated');
@@ -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,o,r=1e4){const i={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}},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(i),signal:n.signal,keepalive:!0}).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,o,r){return new Promise((i,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?i(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(o))})}(t,e,i,r)}const o=new WeakMap;function r(t,e,r,n){e.querySelectorAll("form[data-notix], form[data-notify]").forEach(e=>{e.dataset.notixBound||(e.dataset.notixBound="1",e.addEventListener("submit",n=>{n.preventDefault();const a=Date.now();if(!(a-(o.get(e)??0)<500)){o.set(e,a);try{const o=function(t){const e=i(t,"title")??document.title,o=i(t,"type"),r=i(t,"tag"),n=i(t,"tags"),a=i(t,"priority"),s=i(t,"body"),c=i(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(n)try{const t=JSON.parse(n);l=[...l||[],...Array.isArray(t)?t:[t]]}catch{l=l||[],l.push(n)}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 y=i(t,"payload");if(y)try{m=JSON.parse(y)}catch{}return{title:e,...p&&{body:p},...o&&{type:o},...l&&l.length>0&&{tags:l},...a&&{priority:a},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 n=i(e,"onsuccess");if(n){"function"==typeof window[n]&&(e.dataset.notixCallback=n)}}catch(t){r&&console.warn("[Notix] Form capture failed:",t)}}}))})}function i(t,e){const o=e.charAt(0).toUpperCase()+e.slice(1);return t.dataset[`notix${o}`]??t.dataset[`notify${o}`]}function n(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 a(o,t?.prefix??"notix"):null}class a{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 s{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 c{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=n(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 i=(t instanceof Element?t:document).querySelector("form[data-notix-bound]");if(i instanceof HTMLFormElement&&i.dataset.notixCallback){const t=window[i.dataset.notixCallback];"function"==typeof t&&t(r),delete i.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:l(),page:t||location.pathname,referrer:e||document.referrer||void 0,value:1}};this.notify(o).catch(()=>{}),this.log("Pageview sent:",t||location.pathname)}enableErrorTracking(){"undefined"!=typeof window&&(new s(t=>{this.notify({title:`Ошибки на странице (${t.length})`,type:"error_batch",payload:{errors:t}}).catch(()=>{})}),this.log("Error tracking activated"))}static getVisitorId(){return l()}log(...t){this.debug&&console.log("[Notix]",...t)}}const d="notix_vid";function l(){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 o=t.dataset.endpoint,r="true"===t.dataset.autoCapture,i="true"===t.dataset.debug,n=t.dataset.timeout?parseInt(t.dataset.timeout,10):void 0,a=()=>{const a="true"===t.dataset.metric,s="true"===t.dataset.errors,d=new c({token:e,...o&&{endpoint:o},autoCapture:r,debug:i,metricEnabled:a,errorTrackingEnabled:s,...n&&{timeout:n}});window.notix=d};"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=c,t}({});
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/package.json CHANGED
@@ -1,40 +1,40 @@
1
- {
2
- "name": "@notix-hub/sdk",
3
- "version": "0.3.2",
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
+ }