@notix-hub/sdk 0.1.0 → 0.2.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/README.md +33 -9
- package/dist/index.cjs +43 -12
- package/dist/index.mjs +43 -12
- package/dist/notify.min.js +1 -1
- package/dist/types.d.ts +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -72,27 +72,51 @@ await notix.notify({
|
|
|
72
72
|
|
|
73
73
|
## Автозахват форм
|
|
74
74
|
|
|
75
|
-
Добавьте `data-notify` к форме, и SDK перехватит `submit
|
|
75
|
+
Добавьте `data-notify` к форме, и SDK перехватит `submit`.
|
|
76
|
+
|
|
77
|
+
### Шаблон (рекомендуется)
|
|
76
78
|
|
|
77
79
|
```html
|
|
78
|
-
<form data-notify
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
<form data-notify
|
|
81
|
+
data-notify-type="lead"
|
|
82
|
+
data-notify-tag="Сайт"
|
|
83
|
+
data-notify-title="Новая заявка"
|
|
84
|
+
data-notify-template="Имя: {name}\nТелефон: {phone}\nСообщение: {message}">
|
|
85
|
+
<input name="name" placeholder="Ваше имя" required>
|
|
86
|
+
<input name="phone" placeholder="Телефон">
|
|
87
|
+
<textarea name="message" placeholder="Сообщение"></textarea>
|
|
81
88
|
<button type="submit">Отправить</button>
|
|
82
89
|
</form>
|
|
83
90
|
```
|
|
84
91
|
|
|
92
|
+
### Поля с метками
|
|
93
|
+
|
|
94
|
+
```html
|
|
95
|
+
<form data-notify data-notify-title="Заявка">
|
|
96
|
+
<input name="name" data-notify-label="Имя">
|
|
97
|
+
<input name="phone" data-notify-label="Телефон">
|
|
98
|
+
<button type="submit">Отправить</button>
|
|
99
|
+
</form>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Без шаблона и меток
|
|
103
|
+
|
|
104
|
+
SDK соберёт все поля как `name: value`.
|
|
105
|
+
|
|
85
106
|
Атрибуты:
|
|
86
107
|
|
|
87
108
|
| Атрибут | Описание |
|
|
88
109
|
|---------|----------|
|
|
89
110
|
| `data-notify` | Включает захват формы |
|
|
90
111
|
| `data-notify-title` | Заголовок уведомления (по умолчанию `document.title`) |
|
|
91
|
-
| `data-notify-
|
|
92
|
-
| `data-notify-
|
|
93
|
-
| `data-notify-priority` | Приоритет |
|
|
94
|
-
| `data-notify-
|
|
95
|
-
| `data-notify-
|
|
112
|
+
| `data-notify-type` | Тип уведомления (lead, order, message, crm, alert) |
|
|
113
|
+
| `data-notify-tag` | Тег уведомления |
|
|
114
|
+
| `data-notify-priority` | Приоритет (low, normal, high, urgent) |
|
|
115
|
+
| `data-notify-template` | Шаблон тела с `{field_name}` |
|
|
116
|
+
| `data-notify-body` | Статичный текст (если не нужен шаблон) |
|
|
117
|
+
| `data-notify-fields` | Имена полей через запятую |
|
|
118
|
+
| `data-notify-label` | Человекочитаемое название поля |
|
|
119
|
+
| `data-notify-field` | Пометить конкретное поле |
|
|
96
120
|
|
|
97
121
|
## Яндекс.Метрика
|
|
98
122
|
|
package/dist/index.cjs
CHANGED
|
@@ -12,6 +12,8 @@ function sendWebhook(endpoint, token, payload, timeout = DEFAULT_TIMEOUT) {
|
|
|
12
12
|
...(payload.body !== undefined && { body: payload.body }),
|
|
13
13
|
...(payload.type !== undefined && { notification_type: payload.type }),
|
|
14
14
|
...(payload.priority !== undefined && { priority: payload.priority }),
|
|
15
|
+
...(payload.tag !== undefined && { tag: payload.tag }),
|
|
16
|
+
...(payload.payload !== undefined && { payload: payload.payload }),
|
|
15
17
|
};
|
|
16
18
|
const controller = new AbortController();
|
|
17
19
|
const timer = setTimeout(() => controller.abort(), timeout);
|
|
@@ -110,8 +112,10 @@ function destroyFormCapture() {
|
|
|
110
112
|
function extractPayload(form) {
|
|
111
113
|
const title = form.dataset.notifyTitle ?? form.dataset.notify_title ?? document.title;
|
|
112
114
|
const type = form.dataset.notifyType ?? form.dataset.notify_type;
|
|
115
|
+
const tag = form.dataset.notifyTag ?? form.dataset.notify_tag;
|
|
113
116
|
const priority = form.dataset.notifyPriority ?? form.dataset.notify_priority;
|
|
114
117
|
const staticBody = form.dataset.notifyBody ?? form.dataset.notify_body;
|
|
118
|
+
const template = form.dataset.notifyTemplate ?? form.dataset.notify_template;
|
|
115
119
|
const specifiedFields = (form.dataset.notifyFields ?? form.dataset.notify_fields)
|
|
116
120
|
?.split(',')
|
|
117
121
|
.map((s) => s.trim())
|
|
@@ -121,26 +125,44 @@ function extractPayload(form) {
|
|
|
121
125
|
title,
|
|
122
126
|
body: staticBody,
|
|
123
127
|
...(type && { type }),
|
|
128
|
+
...(tag && { tag }),
|
|
124
129
|
...(priority && { priority: priority }),
|
|
125
130
|
};
|
|
126
131
|
}
|
|
127
|
-
const fieldEntries = [];
|
|
128
132
|
const elements = form.querySelectorAll('input[name], textarea[name], select[name]');
|
|
133
|
+
const fieldMap = new Map();
|
|
129
134
|
elements.forEach((el) => {
|
|
130
135
|
if (!el.name)
|
|
131
136
|
return;
|
|
137
|
+
if (el.type === 'checkbox' || el.type === 'radio') {
|
|
138
|
+
if (!el.checked)
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
132
141
|
if (specifiedFields && !specifiedFields.includes(el.name))
|
|
133
142
|
return;
|
|
134
143
|
const isMarked = el.hasAttribute('data-notify-field');
|
|
135
|
-
if (specifiedFields || isMarked) {
|
|
136
|
-
|
|
144
|
+
if (specifiedFields || isMarked || (!specifiedFields && !template)) {
|
|
145
|
+
const label = el.getAttribute('data-notify-label') ?? el.name;
|
|
146
|
+
fieldMap.set(label, el.value || '');
|
|
137
147
|
}
|
|
138
148
|
});
|
|
139
|
-
|
|
149
|
+
let body;
|
|
150
|
+
if (template) {
|
|
151
|
+
body = template.replace(/\{(\w+)\}/g, (_, key) => {
|
|
152
|
+
if (fieldMap.has(key))
|
|
153
|
+
return fieldMap.get(key);
|
|
154
|
+
const el = form.elements.namedItem(key);
|
|
155
|
+
return el?.value || `{${key}}`;
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
else if (fieldMap.size > 0) {
|
|
159
|
+
body = Array.from(fieldMap, ([k, v]) => `${k}: ${v}`).join('\n');
|
|
160
|
+
}
|
|
140
161
|
return {
|
|
141
162
|
title,
|
|
142
163
|
...(body && { body }),
|
|
143
164
|
...(type && { type }),
|
|
165
|
+
...(tag && { tag }),
|
|
144
166
|
...(priority && { priority: priority }),
|
|
145
167
|
};
|
|
146
168
|
}
|
|
@@ -268,14 +290,23 @@ if (typeof document !== 'undefined') {
|
|
|
268
290
|
const autoCapture = thisScript.dataset.autoCapture !== 'false';
|
|
269
291
|
const debug = thisScript.dataset.debug === 'true';
|
|
270
292
|
const timeout = thisScript.dataset.timeout ? parseInt(thisScript.dataset.timeout, 10) : undefined;
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
293
|
+
const ready = document.readyState !== 'loading';
|
|
294
|
+
const init = () => {
|
|
295
|
+
const notix = new Notix({
|
|
296
|
+
token,
|
|
297
|
+
...(endpoint && { endpoint }),
|
|
298
|
+
autoCapture,
|
|
299
|
+
debug,
|
|
300
|
+
...(timeout && { timeout }),
|
|
301
|
+
});
|
|
302
|
+
window.notix = notix;
|
|
303
|
+
};
|
|
304
|
+
if (ready) {
|
|
305
|
+
init();
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
309
|
+
}
|
|
279
310
|
}
|
|
280
311
|
else if (thisScript.dataset.debug === 'true') {
|
|
281
312
|
console.warn('[Notix] data-token attribute is required on script tag');
|
package/dist/index.mjs
CHANGED
|
@@ -10,6 +10,8 @@ function sendWebhook(endpoint, token, payload, timeout = DEFAULT_TIMEOUT) {
|
|
|
10
10
|
...(payload.body !== undefined && { body: payload.body }),
|
|
11
11
|
...(payload.type !== undefined && { notification_type: payload.type }),
|
|
12
12
|
...(payload.priority !== undefined && { priority: payload.priority }),
|
|
13
|
+
...(payload.tag !== undefined && { tag: payload.tag }),
|
|
14
|
+
...(payload.payload !== undefined && { payload: payload.payload }),
|
|
13
15
|
};
|
|
14
16
|
const controller = new AbortController();
|
|
15
17
|
const timer = setTimeout(() => controller.abort(), timeout);
|
|
@@ -108,8 +110,10 @@ function destroyFormCapture() {
|
|
|
108
110
|
function extractPayload(form) {
|
|
109
111
|
const title = form.dataset.notifyTitle ?? form.dataset.notify_title ?? document.title;
|
|
110
112
|
const type = form.dataset.notifyType ?? form.dataset.notify_type;
|
|
113
|
+
const tag = form.dataset.notifyTag ?? form.dataset.notify_tag;
|
|
111
114
|
const priority = form.dataset.notifyPriority ?? form.dataset.notify_priority;
|
|
112
115
|
const staticBody = form.dataset.notifyBody ?? form.dataset.notify_body;
|
|
116
|
+
const template = form.dataset.notifyTemplate ?? form.dataset.notify_template;
|
|
113
117
|
const specifiedFields = (form.dataset.notifyFields ?? form.dataset.notify_fields)
|
|
114
118
|
?.split(',')
|
|
115
119
|
.map((s) => s.trim())
|
|
@@ -119,26 +123,44 @@ function extractPayload(form) {
|
|
|
119
123
|
title,
|
|
120
124
|
body: staticBody,
|
|
121
125
|
...(type && { type }),
|
|
126
|
+
...(tag && { tag }),
|
|
122
127
|
...(priority && { priority: priority }),
|
|
123
128
|
};
|
|
124
129
|
}
|
|
125
|
-
const fieldEntries = [];
|
|
126
130
|
const elements = form.querySelectorAll('input[name], textarea[name], select[name]');
|
|
131
|
+
const fieldMap = new Map();
|
|
127
132
|
elements.forEach((el) => {
|
|
128
133
|
if (!el.name)
|
|
129
134
|
return;
|
|
135
|
+
if (el.type === 'checkbox' || el.type === 'radio') {
|
|
136
|
+
if (!el.checked)
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
130
139
|
if (specifiedFields && !specifiedFields.includes(el.name))
|
|
131
140
|
return;
|
|
132
141
|
const isMarked = el.hasAttribute('data-notify-field');
|
|
133
|
-
if (specifiedFields || isMarked) {
|
|
134
|
-
|
|
142
|
+
if (specifiedFields || isMarked || (!specifiedFields && !template)) {
|
|
143
|
+
const label = el.getAttribute('data-notify-label') ?? el.name;
|
|
144
|
+
fieldMap.set(label, el.value || '');
|
|
135
145
|
}
|
|
136
146
|
});
|
|
137
|
-
|
|
147
|
+
let body;
|
|
148
|
+
if (template) {
|
|
149
|
+
body = template.replace(/\{(\w+)\}/g, (_, key) => {
|
|
150
|
+
if (fieldMap.has(key))
|
|
151
|
+
return fieldMap.get(key);
|
|
152
|
+
const el = form.elements.namedItem(key);
|
|
153
|
+
return el?.value || `{${key}}`;
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
else if (fieldMap.size > 0) {
|
|
157
|
+
body = Array.from(fieldMap, ([k, v]) => `${k}: ${v}`).join('\n');
|
|
158
|
+
}
|
|
138
159
|
return {
|
|
139
160
|
title,
|
|
140
161
|
...(body && { body }),
|
|
141
162
|
...(type && { type }),
|
|
163
|
+
...(tag && { tag }),
|
|
142
164
|
...(priority && { priority: priority }),
|
|
143
165
|
};
|
|
144
166
|
}
|
|
@@ -266,14 +288,23 @@ if (typeof document !== 'undefined') {
|
|
|
266
288
|
const autoCapture = thisScript.dataset.autoCapture !== 'false';
|
|
267
289
|
const debug = thisScript.dataset.debug === 'true';
|
|
268
290
|
const timeout = thisScript.dataset.timeout ? parseInt(thisScript.dataset.timeout, 10) : undefined;
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
291
|
+
const ready = document.readyState !== 'loading';
|
|
292
|
+
const init = () => {
|
|
293
|
+
const notix = new Notix({
|
|
294
|
+
token,
|
|
295
|
+
...(endpoint && { endpoint }),
|
|
296
|
+
autoCapture,
|
|
297
|
+
debug,
|
|
298
|
+
...(timeout && { timeout }),
|
|
299
|
+
});
|
|
300
|
+
window.notix = notix;
|
|
301
|
+
};
|
|
302
|
+
if (ready) {
|
|
303
|
+
init();
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
307
|
+
}
|
|
277
308
|
}
|
|
278
309
|
else if (thisScript.dataset.debug === 'true') {
|
|
279
310
|
console.warn('[Notix] data-token attribute is required on script tag');
|
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,o,i=1e4){const
|
|
6
|
+
var Notix=function(t){"use strict";function e(t,e,o,i=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.payload&&{payload:o.payload}},r=new AbortController,a=setTimeout(()=>r.abort(),i);return"undefined"!=typeof fetch?fetch(t,{method:"POST",headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(n),signal:r.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,o,i){return new Promise((n,r)=>{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=i,a.onload=()=>{try{const t=JSON.parse(a.responseText);a.status>=200&&a.status<300?n(t):r(new Error(t.error||t.message||`HTTP ${a.status}`))}catch{r(new Error("Invalid response"))}},a.onerror=()=>r(new Error("Network error")),a.ontimeout=()=>r(new Error("Request timeout")),a.send(JSON.stringify(o))})}(t,e,n,i)}const o=new WeakMap;function i(t,e,i){e.querySelectorAll("form[data-notify]").forEach(e=>{e.dataset.notixBound||(e.dataset.notixBound="1",e.addEventListener("submit",n=>{const r=Date.now();if(r-(o.get(e)??0)<500)n.preventDefault();else{o.set(e,r);try{const o=function(t){const e=t.dataset.notifyTitle??t.dataset.notify_title??document.title,o=t.dataset.notifyType??t.dataset.notify_type,i=t.dataset.notifyTag??t.dataset.notify_tag,n=t.dataset.notifyPriority??t.dataset.notify_priority,r=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(r)return{title:e,body:r,...o&&{type:o},...i&&{tag:i},...n&&{priority:n}};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,o)=>{if(c.has(o))return c.get(o);const i=t.elements.namedItem(o);return i?.value||`{${o}}`}):c.size>0&&(u=Array.from(c,([t,e])=>`${t}: ${e}`).join("\n"));return{title:e,...u&&{body:u},...o&&{type:o},...i&&{tag:i},...n&&{priority:n}}}(e);i&&console.log("[Notix] Form captured:",o.title),t(o,{title:o.title,fieldsCount:Object.keys(e.elements).length})}catch(t){i&&console.warn("[Notix] Form capture failed:",t)}}}))})}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 r(o,t?.prefix??"notix"):null}class r{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){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),!1!==t.autoCapture&&this.capture()}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||(i((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")}log(...t){this.debug&&console.log("[Notix]",...t)}}if("undefined"!=typeof document){const t=document.currentScript;if(t){const e=t.dataset.token;if(e){const o=t.dataset.endpoint,i="false"!==t.dataset.autoCapture,n="true"===t.dataset.debug,r=t.dataset.timeout?parseInt(t.dataset.timeout,10):void 0,s=()=>{const t=new a({token:e,...o&&{endpoint:o},autoCapture:i,debug:n,...r&&{timeout:r}});window.notix=t};"loading"!==document.readyState?s():document.addEventListener("DOMContentLoaded",s)}else"true"===t.dataset.debug&&console.warn("[Notix] data-token attribute is required on script tag")}}return t.Notix=a,t}({});
|
package/dist/types.d.ts
CHANGED
|
@@ -3,11 +3,13 @@ export interface NotifyPayload {
|
|
|
3
3
|
title: string;
|
|
4
4
|
body?: string;
|
|
5
5
|
type?: string;
|
|
6
|
+
tag?: string;
|
|
6
7
|
priority?: NotificationPriority;
|
|
7
8
|
payload?: Record<string, unknown>;
|
|
8
9
|
}
|
|
9
10
|
export interface NotifyRequestBody extends NotifyPayload {
|
|
10
11
|
notification_type?: string;
|
|
12
|
+
tag?: string;
|
|
11
13
|
}
|
|
12
14
|
export interface NotixResponse {
|
|
13
15
|
message: string;
|