@fcdm/tracker 0.0.0-stage → 1.0.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 +30 -2
- package/dist/core/api-client.d.ts +12 -0
- package/dist/core/api-client.js +1 -0
- package/dist/core/config.d.ts +4 -0
- package/dist/core/config.js +1 -0
- package/dist/core/index.d.ts +3 -0
- package/dist/core/index.js +1 -0
- package/dist/core/types.d.ts +23 -0
- package/dist/core/types.js +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +1 -0
- package/dist/modules/notification-popup/endpoints.d.ts +8 -0
- package/dist/modules/notification-popup/endpoints.js +1 -0
- package/dist/modules/notification-popup/index.d.ts +15 -0
- package/dist/modules/notification-popup/index.js +1 -0
- package/dist/modules/notification-popup/popup.d.ts +22 -0
- package/dist/modules/notification-popup/popup.js +3 -0
- package/dist/modules/notification-popup/push.d.ts +4 -0
- package/dist/modules/notification-popup/push.js +1 -0
- package/dist/modules/notification-popup/types.d.ts +24 -0
- package/dist/modules/notification-popup/types.js +1 -0
- package/dist/modules/tracking/endpoints.d.ts +5 -0
- package/dist/modules/tracking/endpoints.js +1 -0
- package/dist/modules/tracking/index.d.ts +3 -0
- package/dist/modules/tracking/index.js +1 -0
- package/dist/sw.dev.js +1 -0
- package/dist/sw.local.js +1 -0
- package/dist/sw.prod.js +1 -0
- package/package.json +25 -3
package/README.md
CHANGED
|
@@ -1,3 +1,31 @@
|
|
|
1
|
-
|
|
1
|
+
Пример использования на сайте
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# Инициализация
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import {Tracker} from 'tracking';
|
|
7
|
+
|
|
8
|
+
Tracker.configure({
|
|
9
|
+
baseUrl: BASE_URL,
|
|
10
|
+
timeout: 15_000,
|
|
11
|
+
onError: (err, url) => {
|
|
12
|
+
console.error('[tracking]', err.status, url);
|
|
13
|
+
},
|
|
14
|
+
service: 'service_name',
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
Tracker.tracking.init();
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
# Подключение модуля Web-push
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import {Tracker} from 'tracking';
|
|
24
|
+
|
|
25
|
+
Tracker.notifications.init({
|
|
26
|
+
delay: 5_000,
|
|
27
|
+
rememberDismiss: false,
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
В зависимости от среды необходимо скопировать файл sw.dev.js или sw.prod.js в корень сайта (что бы был доступ по /sw.js)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type QueryParams = Record<string, string | number | boolean | null | undefined>;
|
|
2
|
+
export declare class ApiClient {
|
|
3
|
+
private prefix;
|
|
4
|
+
private opts;
|
|
5
|
+
constructor(prefix?: string);
|
|
6
|
+
request<T = unknown>(path: string, init?: RequestInit): Promise<T>;
|
|
7
|
+
get<T>(path: string, query?: QueryParams): Promise<T>;
|
|
8
|
+
post<T>(path: string, body?: unknown): Promise<T>;
|
|
9
|
+
put<T>(path: string, body?: unknown): Promise<T>;
|
|
10
|
+
delete<T>(path: string, body?: unknown): Promise<T>;
|
|
11
|
+
}
|
|
12
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{getConfig}from"./config.js";class ApiClient{constructor(t=""){this.prefix=t.replace(/\/$/,""),this.opts={timeout:1e4}}request(t,e={}){let i=getConfig(),r=""+i.baseUrl+this.prefix+t;let s={credentials:"include",headers:{"Content-Type":"application/json",...i.headers,...e.headers??{},"X-TrService":i.service},...e},o=(i.onRequest&&(s=i.onRequest(r,s)),new AbortController);t=this.opts.timeout??i.timeout??1e4;let n=setTimeout(()=>o.abort(),t);return s.signal=o.signal,fetch(r,s).then(async t=>{if(t.ok)return(t.headers.get("content-type")??"").includes("application/json")?t.json():await t.text();throw t={status:t.status,statusText:t.statusText,url:r,body:await t.text().catch(()=>{})},i.onError?.(t,r),t}).finally(()=>clearTimeout(n))}get(t,e){return this.request(buildPath(t,e))}post(t,e){return this.request(t,{method:"POST",body:void 0!==e?JSON.stringify(e):void 0})}put(t,e){return this.request(t,{method:"PUT",body:void 0!==e?JSON.stringify(e):void 0})}delete(t,e){return this.request(t,{method:"DELETE",body:void 0!==e?JSON.stringify(e):void 0})}}function buildPath(t,e){if(!e)return t;var i,r,s=new URLSearchParams;for([i,r]of Object.entries(e))null!=r&&s.append(i,String(r));e=s.toString();return e?t.includes("?")?t+"&"+e:t+"?"+e:t}export{ApiClient};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
let _config=null;function configure(n){_config=n}function getConfig(){if(!_config)throw new Error("[tracking] Не настроено. Вызовите Tracker.configure({...}) до использования модулей.");if(""===_config.service)throw new Error("[tracking] Укажите параметр service. Tracker.configure({service:'name'})");return _config}function hasConfig(){return null!==_config}export{configure,getConfig,hasConfig};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{configure,getConfig,hasConfig}from"./config.js";export{ApiClient}from"./api-client.js";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Общая конфигурация подключения к бэкенду. Устанавливается один раз. */
|
|
2
|
+
export interface TrackerConfig {
|
|
3
|
+
/** Базовый URL бэкенда, например `https://api.example.com` */
|
|
4
|
+
baseUrl: string;
|
|
5
|
+
/** Дополнительные заголовки для всех запросов */
|
|
6
|
+
headers?: Record<string, string>;
|
|
7
|
+
/** Тайм-аут запросов, мс */
|
|
8
|
+
timeout?: number;
|
|
9
|
+
/** Функция-хук на каждый запрос (логирование, трассировка и т.п.) */
|
|
10
|
+
onRequest?: (url: string, init: RequestInit) => RequestInit;
|
|
11
|
+
/** Функция-хук на ошибку */
|
|
12
|
+
onError?: (error: TrackerApiError, url: string) => void;
|
|
13
|
+
/** Название сервиса */
|
|
14
|
+
service: string;
|
|
15
|
+
}
|
|
16
|
+
export interface TrackerApiError {
|
|
17
|
+
status: number;
|
|
18
|
+
statusText: string;
|
|
19
|
+
body?: unknown;
|
|
20
|
+
url: string;
|
|
21
|
+
}
|
|
22
|
+
/** Общие события */
|
|
23
|
+
export type TrackerEventMap = Record<string, unknown[]>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export { configure, getConfig, hasConfig } from "./core/config.js";
|
|
2
|
+
export { ApiClient } from "./core/api-client.js";
|
|
3
|
+
export type { TrackerConfig, TrackerApiError } from "./core/types.js";
|
|
4
|
+
export { tracking } from "./modules/tracking/index.js";
|
|
5
|
+
export { notifications } from "./modules/notification-popup/index.js";
|
|
6
|
+
export type { NotificationPopupOptions } from "./modules/notification-popup/types.ts";
|
|
7
|
+
import { configure } from "./core/index.js";
|
|
8
|
+
declare const Tracker: {
|
|
9
|
+
readonly configure: typeof configure;
|
|
10
|
+
readonly tracking: {
|
|
11
|
+
init(): void;
|
|
12
|
+
};
|
|
13
|
+
readonly notifications: {
|
|
14
|
+
init(options?: import("./modules/notification-popup/types.ts").NotificationPopupOptions): void;
|
|
15
|
+
unsubscribe(): Promise<void>;
|
|
16
|
+
isSubscribed(): Promise<boolean>;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
export { Tracker };
|
|
20
|
+
export type Tracker = typeof Tracker;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{configure,getConfig,hasConfig}from"./core/config.js";export{ApiClient}from"./core/api-client.js";export{tracking}from"./modules/tracking/index.js";export{notifications}from"./modules/notification-popup/index.js";import{configure}from"./core/index.js";import{tracking}from"./modules/tracking/index.js";import{notifications}from"./modules/notification-popup/index.js";let Tracker={configure:configure,tracking:tracking,notifications:notifications};export{Tracker};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { PushSubscriptionJSON } from "./types.js";
|
|
2
|
+
export declare const notificationApi: {
|
|
3
|
+
getVapidPublicKey: () => Promise<string>;
|
|
4
|
+
subscribe: (sub: PushSubscriptionJSON) => Promise<Record<string, never>>;
|
|
5
|
+
unsubscribe: (sub: PushSubscriptionJSON) => Promise<Record<string, never>>;
|
|
6
|
+
status: (sub: PushSubscriptionJSON) => Promise<boolean>;
|
|
7
|
+
delivery: (id: string) => Promise<Record<string, never>>;
|
|
8
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{ApiClient}from"../../core/index.js";let EndPoints={vapidPublicKey:"/key",subscribe:"/subscribe",unsubscribe:"/unsubscribe",status:"/subscribe-status",delivery:"/delivery"},client=new ApiClient("/webpush"),notificationApi={getVapidPublicKey:()=>client.get(EndPoints.vapidPublicKey).then(i=>i.data.key),subscribe:i=>client.post(EndPoints.subscribe,i),unsubscribe:i=>client.delete(EndPoints.unsubscribe,i),status:i=>client.post(EndPoints.status,{endpoint:i.endpoint}).then(i=>i.data.status),delivery:i=>client.get(EndPoints.delivery,{id:i})};export{notificationApi};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { NotificationPopupOptions } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Инициализация модуля Notification Popup.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* import { notifications } from 'tracking';
|
|
7
|
+
* notifications.tracking({ title: 'Следите за новостями' });
|
|
8
|
+
*/
|
|
9
|
+
export declare const notifications: {
|
|
10
|
+
init(options?: NotificationPopupOptions): void;
|
|
11
|
+
/** Отписка (вызывается из настроек пользователя) */
|
|
12
|
+
unsubscribe(): Promise<void>;
|
|
13
|
+
/** Проверить статус подписки */
|
|
14
|
+
isSubscribed(): Promise<boolean>;
|
|
15
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{NotificationPopup}from"./popup.js";import{notificationApi}from"./endpoints.js";import{getPushSubscription,unsubscribeFromPush}from"./push.js";let notifications={init(i={}){let t=new NotificationPopup(i,{onSubscribe:async()=>{var i=await notificationApi.getVapidPublicKey(),i=await getPushSubscription(i);if(!i)throw new Error("Push API недоступен");await notificationApi.subscribe(i)}});i=()=>t.start();"complete"===document.readyState?i():window.addEventListener("load",i,{once:!0})},async unsubscribe(){await unsubscribeFromPush()},async isSubscribed(){var i=(await import("./push.js")).isSubscribed;return i()}};export{notifications};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { NotificationPopupOptions } from "./types.js";
|
|
2
|
+
interface PopupHandlers {
|
|
3
|
+
onSubscribe: () => Promise<void>;
|
|
4
|
+
}
|
|
5
|
+
export declare class NotificationPopup {
|
|
6
|
+
private opts;
|
|
7
|
+
private handlers;
|
|
8
|
+
private el;
|
|
9
|
+
private timer;
|
|
10
|
+
private escHandler;
|
|
11
|
+
constructor(opts: NotificationPopupOptions | undefined, handlers: PopupHandlers);
|
|
12
|
+
start(): void;
|
|
13
|
+
destroy(): void;
|
|
14
|
+
private show;
|
|
15
|
+
dismiss(): void;
|
|
16
|
+
private render;
|
|
17
|
+
private handleSubscribe;
|
|
18
|
+
private isDismissedToday;
|
|
19
|
+
private markDismissed;
|
|
20
|
+
private esc;
|
|
21
|
+
}
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{isSubscribed}from"./push.js";let CSS='.tr-notify { position: fixed; inset: 0; z-index: 9999; display: flex; align-items: flex-start; justify-content: center; padding-top: 24px; padding-left: 24px; padding-right: 24px; padding-bottom: 24px; background: transparent; pointer-events: none; opacity: 0; transition: opacity 0.25s ease; font-family: "TT Hoves", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; } .tr-notify--visible { opacity: 1; pointer-events: auto; } .tr-notify__card { width: 100%; max-width: 346px; padding: 12px; background: var(--Colors-Text, #fff); box-shadow: 0px 1px 20px rgba(0, 0, 0, 0.15); overflow: hidden; border-radius: 20px; display: flex; flex-direction: column; justify-content: flex-start; align-items: center; gap: 12px; transform: translateY(-16px) scale(0.97); transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); } .tr-notify--visible .tr-notify__card { transform: translateY(0) scale(1); } /* ── top row: icon | text | close ── */ .tr-notify__row { flex: 1 1 0; display: flex; flex-direction: row; justify-content: flex-start; align-items: flex-start; gap: 12px; width: 100%; } .tr-notify__icon { width: 49px; height: 75px; position: relative; background: #fff; overflow: hidden; flex-shrink: 0; } /* text block */ .tr-notify__text { padding-bottom: 8px; display: flex; flex-direction: column; justify-content: flex-start; align-items: flex-start; gap: 16px; flex: 1; min-width: 0; } .tr-notify__title { color: #1650B1; font-size: 16px; font-weight: 600; line-height: 120%; margin: 0; word-wrap: break-word; } .tr-notify__subtitle { width: 100%; display: flex; flex-direction: column; justify-content: flex-start; align-items: flex-start; gap: 4px; color: #1650B1; font-size: 14px; font-weight: 300; line-height: 120%; word-wrap: break-word; } /* close button */ .tr-notify__close { width: 26px; height: 26px; padding: 4px; border-radius: 1000px; display: flex; align-items: center; justify-content: center; border: none; background: transparent; cursor: pointer; flex-shrink: 0; transition: background 0.15s; } .tr-notify__close:hover { background: rgba(22, 80, 177, 0.08); } .tr-notify__close svg { display: block; } /* CTA button */ .tr-notify__btn { width: 100%; height: 40px; padding: 10px 24px; background: #1650B1; border-radius: 4px; border: none; display: flex; flex-direction: column; justify-content: center; align-items: center; color: #fff; font-size: 16px; font-family: inherit; font-weight: 400; line-height: 20px; letter-spacing: 0.32px; cursor: pointer; transition: background 0.15s, opacity 0.15s; } .tr-notify__btn:hover:not(:disabled) { background: #0e3d8a; } .tr-notify__btn:disabled { opacity: 0.55; cursor: default; } /* status line */ .tr-notify__status { width: 100%; text-align: center; font-size: 13px; color: #1650B1; margin: 0; } @media (max-width: 420px) { .tr-notify { padding-top: 12px; padding-left: 12px; padding-right: 12px; } .tr-notify__card { border-radius: 16px; padding: 10px; gap: 10px; } .tr-notify__row { gap: 10px; } }';function injectStyles(t){var e;"undefined"!=typeof document&&((e=document.querySelector("style[data-tr-notify]"))?t&&(e.textContent+=`
|
|
2
|
+
`+t):((e=document.createElement("style")).setAttribute("data-tr-notify",""),e.textContent=CSS+(t?`
|
|
3
|
+
`+t:""),document.head.appendChild(e)))}let DEFAULTS={delay:3e4,title:"Хочешь получить доступ<br/>к выгодным предложениям?",message:"Подпишись на уведомления<br/>от ФК «Динамо» Москва.",subscribeButtonText:"Подписаться",dismissButtonText:"",showIfSubscribed:!1,rememberDismiss:!0,className:"",css:""},STORAGE_KEY="tr-notify-dismissed-at",CLOSE_SVG='<svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9 9L17 17" stroke="#1650B1" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M17 9L9 17" stroke="#1650B1" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>',ICON_SVG='<svg width="51" height="75" viewBox="0 0 51 75" fill="none" xmlns="http://www.w3.org/2000/svg"> <g clip-path="url(#clip0_11584_52786)"> <rect width="51" height="75" fill="white"/> <g clip-path="url(#clip1_11584_52786)"> <path d="M28.0721 13.8829C27.8938 13.8829 27.7169 13.8277 27.5669 13.7202C27.2641 13.5022 27.1367 13.1117 27.2528 12.7579L28.4443 9.07724L25.3283 6.80035C25.0283 6.5796 24.9023 6.19186 25.0183 5.83809C25.133 5.48432 25.4641 5.24516 25.8363 5.24516H29.6797L30.8655 1.56734C30.9801 1.21215 31.3113 0.970165 31.6849 0.970165C32.0584 0.970165 32.3896 1.21215 32.5028 1.56734L33.6886 5.24516H37.5292C37.9028 5.24516 38.2325 5.48432 38.3485 5.83809C38.4646 6.19328 38.3386 6.58101 38.0386 6.80035L34.9254 9.07724L36.1198 12.7565C36.2358 13.1117 36.1084 13.5008 35.8056 13.7187C35.6556 13.8277 35.4787 13.8829 35.3004 13.8829C35.1235 13.8829 34.9438 13.8277 34.7938 13.7173L31.6834 11.4489L28.5759 13.7173C28.4245 13.8277 28.2462 13.8829 28.0679 13.8829H28.0721ZM13.7896 13.8829C13.6127 13.8829 13.4344 13.8277 13.2844 13.7187C12.983 13.5008 12.8556 13.1103 12.9702 12.7565L14.1646 9.07583L11.0514 6.79894C10.7514 6.57818 10.6254 6.19045 10.7415 5.83667C10.8575 5.4829 11.1872 5.24375 11.5594 5.24375H15.4L16.5872 1.56592C16.7004 1.21073 17.0316 0.96875 17.4051 0.96875C17.7787 0.96875 18.1099 1.21073 18.2245 1.56592L19.4103 5.24375H23.2537C23.6259 5.24375 23.9556 5.4829 24.0716 5.83667C24.1877 6.19186 24.0632 6.5796 23.7603 6.79894L20.6443 9.07724L21.8372 12.7565C21.9518 13.1117 21.8259 13.4994 21.5231 13.7187C21.3731 13.8277 21.1948 13.8815 21.0179 13.8815C20.8396 13.8815 20.6627 13.8263 20.5099 13.7159L17.4037 11.4489L14.2933 13.7173C14.1419 13.8277 13.9636 13.8829 13.7853 13.8829H13.7896ZM23.4745 26.0357C24.5316 26.0853 25.5816 26.19 26.6117 26.3485C26.8367 23.4999 26.4717 22.9211 26.2169 22.8433C26.1731 22.8305 26.1292 22.8235 26.0839 22.8235C25.5108 22.8249 24.4042 23.8296 23.4745 26.0357ZM22.225 31.4173C22.2051 31.7244 22.1924 32.0301 22.191 32.3343C22.1882 32.473 22.1867 32.6032 22.1882 32.7334C22.1952 33.1607 22.2179 33.5838 22.2561 33.9956C22.3594 33.9107 22.4599 33.8258 22.5589 33.7409C23.3415 33.0588 24.0037 32.3485 24.5613 31.59C24.3448 31.5574 24.1297 31.5305 23.9146 31.5065C23.4122 31.447 22.8985 31.4173 22.3863 31.4173C22.3325 31.4173 22.2787 31.4173 22.225 31.4173ZM22.8136 66.7296C23.5693 66.8612 24.2726 66.9249 24.9405 66.9249C25.8292 66.9249 26.6627 66.806 27.4212 66.5725C36.0745 63.8584 38.0683 56.2466 38.2169 50.34C38.4391 41.6697 35.1759 35.4815 29.0117 32.8268C28.0254 34.2999 26.8283 35.7942 25.4443 37.2772C24.9051 37.8475 24.3462 38.4093 23.7773 38.9513C25.2589 41.3824 27.7566 42.9164 31.2136 43.5206C31.4528 43.5617 31.6636 43.7032 31.7924 43.9084C31.9212 44.1136 31.9594 44.364 31.8943 44.5989C28.9905 55.089 26.1603 62.0074 22.8136 66.7296ZM9.79052 68.2353C9.8825 69.0914 10.3 69.1989 11.057 69.3235C11.1674 69.3404 11.2721 69.3603 11.3641 69.3787C11.6344 69.373 11.9061 69.3461 12.175 69.2952C12.8471 69.1735 13.4783 68.9088 14.0683 68.5041C13.116 68.0145 12.0716 67.532 11.5566 67.532V66.6702L11.5339 67.532C10.215 67.5871 9.89382 67.9848 9.79052 68.2353ZM10.8434 74.1461C8.28769 74.1461 5.93014 73.1824 4.53769 71.5692C3.47071 70.331 3.03486 68.79 3.28392 67.1131C3.72826 64.0862 6.6108 62.7065 9.10137 62.7065C9.21458 62.7065 9.32637 62.7093 9.43391 62.7164C11.2962 62.7518 13.5858 63.6404 16.0099 64.5815C16.2787 64.6862 16.549 64.7909 16.8235 64.8956C17.3103 63.7848 17.6599 62.5423 17.8268 61.3254C18.7141 54.6871 18.8613 49.9551 18.3518 44.4489C18.3207 44.0909 18.2882 43.7386 18.2556 43.3848C16.825 44.3301 15.4396 45.0956 14.1943 45.6249C14.0853 45.6716 13.9721 45.6942 13.8575 45.6942C13.6933 45.6942 13.5278 45.6475 13.3849 45.5541C13.1457 45.3956 12.9985 45.1296 12.9971 44.8409L12.9504 39.8669C12.9476 39.54 13.1287 39.24 13.4202 39.0914C13.9339 38.8296 14.4462 38.5735 14.9528 38.3187C15.8783 37.8546 16.7627 37.4117 17.6188 36.9588C17.5099 35.9103 17.3995 34.8263 17.2778 33.6956C17.2608 33.4989 17.2396 33.3206 17.2198 33.1381L17.1957 32.9159C17.1844 32.7956 17.1731 32.6739 17.1632 32.548C13.3254 34.3169 10.9764 37.8659 10.9566 42.0518C10.9509 43.1287 11.1221 44.5904 11.9698 45.4437C12.5004 45.9772 13.2419 46.2362 14.241 46.2362C14.4929 46.2362 14.7632 46.2178 15.0433 46.1838C15.0787 46.1796 15.1127 46.1782 15.1481 46.1782C15.3674 46.1782 15.5811 46.2617 15.7424 46.4173C15.9278 46.5942 16.0254 46.8475 16.0056 47.1051C15.7113 50.9626 12.5033 53.7631 8.37543 53.7631C6.88533 53.7631 5.33014 53.415 3.75089 52.7301C3.67589 52.6975 3.60373 52.6522 3.54005 52.5985C0.346179 49.9084 -0.742028 45.431 0.626369 40.6197C2.20703 35.0697 7.59146 28.0876 17.599 26.3796C18.915 19.631 22.3933 15.6164 26.9726 15.6037C30.5075 15.8074 32.3415 19.7428 32.3528 23.5254C32.3599 24.8088 32.1235 26.1744 31.6495 27.5966C41.8382 31.123 48.6264 39.8216 49.0297 49.9848C49.4386 63.2074 40.6084 73.4881 28.4867 73.907C28.3665 73.9126 28.2448 73.914 28.1231 73.914C24.9476 73.914 21.5627 72.4466 18.8655 71.0711C17.2 72.431 15.4113 73.3494 13.5419 73.8065C12.8995 73.9622 12.2443 74.0669 11.5948 74.1164C11.3457 74.1376 11.0952 74.1461 10.8434 74.1461Z" fill="#F7F8FA"/> <path d="M17.4047 1.82812L18.7844 6.10171H23.2533L19.6349 8.74794L21.0174 13.0201L17.4033 10.381L13.7849 13.0201L15.1703 8.74794L11.5547 6.10171H16.0236L17.4047 1.82812Z" fill="#AF915A"/> <path d="M31.6845 1.82812L33.0628 6.10171H37.5303L33.9161 8.74794L35.3015 13.0201L31.6845 10.381L28.069 13.0201L29.4529 8.74794L25.8359 6.10171H30.3062L31.6845 1.82812Z" fill="#AF915A"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M39.0744 50.3578C38.8678 58.554 35.3767 64.9757 27.6716 67.3927C25.6593 68.0139 23.4871 67.8313 21.3178 67.2908C25.5857 61.8455 28.5857 53.3125 31.0607 44.3663C26.6541 43.5964 24.08 41.4611 22.6918 38.7823C23.4164 38.1144 24.1267 37.4125 24.8102 36.688C26.2593 35.1356 27.5923 33.47 28.6734 31.762C35.9442 34.5186 39.2994 41.5375 39.0744 50.3578ZM21.3249 32.3252C21.3305 31.7408 21.3673 31.1563 21.4324 30.5861C22.2701 30.5309 23.1376 30.5464 24.0065 30.6483C24.69 30.7247 25.3508 30.838 25.989 30.9724C25.215 32.3026 24.2371 33.4148 23.1178 34.3898C22.6395 34.803 22.1343 35.1922 21.6065 35.563C21.4282 34.6375 21.3404 33.6936 21.3234 32.7455C21.3206 32.6068 21.322 32.4653 21.3249 32.3252ZM26.4645 22.0205C27.5244 22.346 27.7409 23.8983 27.3772 27.3526C25.7201 27.0413 24.0008 26.8743 22.239 26.8644C23.2833 23.7314 25.0197 21.5776 26.4645 22.0205ZM12.3291 70.1394C11.9881 70.2045 11.6357 70.2398 11.2678 70.2398C10.6083 70.0686 9.03759 70.1733 8.9159 68.1342C9.2159 67.0346 10.3211 66.7205 11.4984 66.6696C12.3447 66.637 13.7923 67.3644 15.5881 68.3351C14.698 69.2422 13.6154 69.9045 12.3291 70.1394ZM48.165 50.0196C47.7602 39.8394 40.5815 31.271 30.5343 28.138C31.1456 26.5842 31.4966 25.029 31.4895 23.529C31.4782 19.9389 29.7984 16.629 26.9711 16.4648C21.7466 16.479 19.2022 22.0729 18.3333 27.1347C2.76023 29.4144 -3.41807 45.6144 4.09042 51.9398C10.147 54.5691 14.8069 51.4318 15.1423 47.038C11.3414 47.5049 10.0734 45.1106 10.0918 42.0469C10.1159 36.879 13.4329 32.8743 17.9711 31.2936C17.9767 31.8497 18.0008 32.362 18.0475 32.8247C18.0744 33.0865 18.1055 33.3427 18.1282 33.603C18.2725 34.9431 18.4027 36.2153 18.5286 37.4436C17.0286 38.2587 15.431 39.0299 13.805 39.8578L13.8532 44.8332C15.4069 44.171 17.1687 43.1394 18.9687 41.8417C19.048 42.688 19.1329 43.5257 19.2036 44.3691C19.689 49.6007 19.6282 54.3186 18.6758 61.4408C18.4791 62.8913 18.0121 64.5214 17.2565 65.9832C14.2749 64.8625 11.4999 63.6172 9.3857 63.5747C7.39042 63.4714 4.54183 64.4478 4.13146 67.2384C3.5258 71.345 7.7258 73.5568 11.5253 73.2582C12.1451 73.2101 12.7494 73.1125 13.3352 72.9695C15.3234 72.4842 17.1164 71.471 18.748 70.0417C21.7933 71.6309 25.2928 73.1606 28.4555 73.0474C40.297 72.6384 48.5541 62.5389 48.1678 50.021L48.165 50.0196Z" fill="#1650B1"/> </g> </g> <defs> <clipPath id="clip0_11584_52786"> <rect width="51" height="75" fill="white"/> </clipPath> <clipPath id="clip1_11584_52786"> <rect width="49" height="75" fill="white"/> </clipPath> </defs> </svg>';class NotificationPopup{constructor(t={},e){this.el=null,this.timer=null,this.escHandler=null,this.opts={...DEFAULTS,...t},this.handlers=e}start(){this.isDismissedToday()||(this.timer=window.setTimeout(()=>{this.show()},this.opts.delay))}destroy(){null!==this.timer&&window.clearTimeout(this.timer),this.timer=null,this.escHandler&&document.removeEventListener("keydown",this.escHandler),this.el?.remove(),this.el=null}async show(){if(!this.el){try{if(await isSubscribed()&&!this.opts.showIfSubscribed)return}catch{}this.render()}}dismiss(){this.opts.rememberDismiss&&this.markDismissed(),this.destroy()}render(){if(!this.el){injectStyles(this.opts.css);let t=document.createElement("div");t.className=("tr-notify "+this.opts.className).trim(),t.setAttribute("role","dialog"),t.setAttribute("aria-modal","true"),t.innerHTML=`<div class="tr-notify__card"> <div class="tr-notify__row"> <div class="tr-notify__icon"> ${ICON_SVG} </div> <!-- text --> <div class="tr-notify__text"> <div class="tr-notify__title">${this.esc(this.opts.title)}</div> <div class="tr-notify__subtitle">${this.esc(this.opts.message)}</div> </div> <!-- close --> <button class="tr-notify__close" aria-label="Закрыть"> ${CLOSE_SVG} </button> </div> <!-- CTA --> <button class="tr-notify__btn"> ${this.esc(this.opts.subscribeButtonText)} </button> <!-- status --> <p class="tr-notify__status" hidden></p> </div>`,document.body.appendChild(t),(this.el=t).querySelector(".tr-notify__close").addEventListener("click",()=>this.dismiss()),t.querySelector(".tr-notify__btn").addEventListener("click",()=>{this.handleSubscribe(t)}),this.escHandler=t=>{"Escape"===t.key&&this.dismiss()},document.addEventListener("keydown",this.escHandler),requestAnimationFrame(()=>t.classList.add("tr-notify--visible"))}}async handleSubscribe(e){var i=e.querySelector(".tr-notify__status"),e=e.querySelector(".tr-notify__btn");e.disabled=!0,i.hidden=!1,i.textContent="Запрашиваем разрешение…";try{await this.handlers.onSubscribe(),i.textContent="✅ Вы подписаны!",setTimeout(()=>this.dismiss(),1500)}catch(t){console.log("[TR] Error: "+t),e.disabled=!1,i.textContent=t instanceof Error&&"NotAllowedError"===t.name?"Разрешение не выдано. Попробуйте позже.":"Что-то пошло не так. Попробуйте ещё раз."}}isDismissedToday(){try{var t,e=Number(localStorage.getItem(STORAGE_KEY));return e?((t=new Date).setHours(0,0,0,0),e>=t.getTime()):!1}catch{return!1}}markDismissed(){try{localStorage.setItem(STORAGE_KEY,String(Date.now()))}catch{}}esc(t){return t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/<br\s*\/?>/g,"<br/>")}}export{NotificationPopup};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { PushSubscriptionJSON } from "./types.js";
|
|
2
|
+
export declare function getPushSubscription(vapidKey?: string): Promise<PushSubscriptionJSON | null>;
|
|
3
|
+
export declare function isSubscribed(): Promise<boolean>;
|
|
4
|
+
export declare function unsubscribeFromPush(): Promise<void>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{notificationApi}from"./endpoints.js";let SW_URL="/sw.js";function urlBase64ToUint8Array(i){var i=(i+"=".repeat((4-i.length%4)%4)).replace(/-/g,"+").replace(/_/g,"/"),r=atob(i),i=new ArrayBuffer(r.length),e=new Uint8Array(i);for(let i=0;i<r.length;i++)e[i]=r.charCodeAt(i);return e}async function getRegistration(){let r=await navigator.serviceWorker.register(SW_URL);return navigator.serviceWorker.controller||await new Promise(i=>{if(r.active)return i();r.addEventListener("statechange",()=>{r.active&&i()},{once:!0})}),r}async function getPushSubscription(i){if(!("serviceWorker"in navigator&&"PushManager"in window))return null;var r=await getRegistration();let e=await r.pushManager.getSubscription();return(e=!e&&i?await r.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:urlBase64ToUint8Array(i)}):e)?e.toJSON():null}async function isSubscribed(){var i;return"serviceWorker"in navigator&&"PushManager"in window&&!!(i=await(await navigator.serviceWorker.getRegistration())?.pushManager.getSubscription())&¬ificationApi.status(i.toJSON())}async function unsubscribeFromPush(){if("serviceWorker"in navigator&&"PushManager"in window){var i=await(await navigator.serviceWorker.getRegistration())?.pushManager.getSubscription();if(i){await i.unsubscribe();try{await notificationApi.unsubscribe(i.toJSON())}catch{console.warn("[tracking:notifications] Локальная отписка OK, бэкенд недоступен.")}}}}export{getPushSubscription,isSubscribed,unsubscribeFromPush};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface NotificationPopupOptions {
|
|
2
|
+
/** Задержка показа после `tracking`, мс (default 30 000) */
|
|
3
|
+
delay?: number;
|
|
4
|
+
title?: string;
|
|
5
|
+
message?: string;
|
|
6
|
+
subscribeButtonText?: string;
|
|
7
|
+
dismissButtonText?: string;
|
|
8
|
+
/** Показывать ли попап, если пользователь уже подписан */
|
|
9
|
+
showIfSubscribed?: boolean;
|
|
10
|
+
/** Запомнить закрытие на сегодня (localStorage) */
|
|
11
|
+
rememberDismiss?: boolean;
|
|
12
|
+
/** Дополнительный CSS-класс */
|
|
13
|
+
className?: string;
|
|
14
|
+
css?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface PushSubscriptionJSON {
|
|
17
|
+
endpoint: string;
|
|
18
|
+
expirationTime: number | null;
|
|
19
|
+
keys: {
|
|
20
|
+
p256dh: string;
|
|
21
|
+
auth: string;
|
|
22
|
+
};
|
|
23
|
+
user_agent: string;
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{ApiClient}from"../../core/index.js";let EndPoints={init:"/load"},client=new ApiClient("/tr"),trackingApi={init:()=>client.get(EndPoints.init)};export{trackingApi};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{trackingApi}from"./endpoints.js";let tracking={init(){trackingApi.init()}};export{tracking};
|
package/dist/sw.dev.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
self.addEventListener("install",function(t){t.waitUntil(self.skipWaiting())}),self.addEventListener("activate",function(t){t.waitUntil(clients.claim()),console.log("[Service Worker] Сброшен и успешно обновлен до новой версии!")}),self.addEventListener("push",t=>{let i;try{i=t.data?t.data.json():{}}catch{i={}}var n=i.title||"Уведомление",e={body:i.body||"",icon:i.icon||"/icons/icon-192x192.png",badge:i.badge||"/icons/icon-192x192.png",vibrate:[100,50,100],actions:i.actions||[],data:{url:i.url||"/",id:i.id}},o=fetch("https://t-dev.fcdynamo.ru/v1/webpush/delivery?id="+i.id).catch(t=>console.error("Не удалось отправить отчет о доставке:",t));t.waitUntil(Promise.all([o,self.registration.showNotification(n,e)]))}),self.addEventListener("notificationclick",function(n){n.notification.close();var t=fetch("https://t-dev.fcdynamo.ru/v1/webpush/open?id="+n.notification.data?.id).catch(t=>console.error("Не удалось отправить отчет о переходе:",t));let i;"accept"!==n.action&&"decline"!==n.action&&(i=clients.matchAll({type:"window",includeUncontrolled:!0}).then(function(t){for(var i of t)if(i.url===n.notification.data?.url&&"focus"in i)return i.focus();if(clients.openWindow)return clients.openWindow(n.notification.data?.url)})),n.waitUntil(Promise.all([t,i]))}),self.addEventListener("activate",t=>{console.log("[Service Worker] Activate"),t.waitUntil(self.registration.pushManager.getSubscriptions().then(()=>{}))});
|
package/dist/sw.local.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
self.addEventListener("install",function(i){i.waitUntil(self.skipWaiting())}),self.addEventListener("activate",function(i){i.waitUntil(clients.claim()),console.log("[Service Worker] Сброшен и успешно обновлен до новой версии!")}),self.addEventListener("push",i=>{let t;try{t=i.data?i.data.json():{}}catch{t={}}var n=t.title||"Уведомление",e={body:t.body||"",icon:t.icon||"/icons/icon-192x192.png",badge:t.badge||"/icons/icon-192x192.png",vibrate:[100,50,100],actions:t.actions||[],data:{url:t.url||"/",id:t.id}},o=fetch("http://127.0.0.1:4000/v1/webpush/delivery?id="+t.id).catch(i=>console.error("Не удалось отправить отчет о доставке:",i));i.waitUntil(Promise.all([o,self.registration.showNotification(n,e)]))}),self.addEventListener("notificationclick",function(n){n.notification.close();var i=fetch("http://127.0.0.1:4000/v1/webpush/open?id="+n.notification.data?.id).catch(i=>console.error("Не удалось отправить отчет о переходе:",i));let t;"accept"!==n.action&&"decline"!==n.action&&(t=clients.matchAll({type:"window",includeUncontrolled:!0}).then(function(i){for(var t of i)if(t.url===n.notification.data?.url&&"focus"in t)return t.focus();if(clients.openWindow)return clients.openWindow(n.notification.data?.url)})),n.waitUntil(Promise.all([i,t]))}),self.addEventListener("activate",i=>{console.log("[Service Worker] Activate"),i.waitUntil(self.registration.pushManager.getSubscriptions().then(()=>{}))});
|
package/dist/sw.prod.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
self.addEventListener("install",function(t){t.waitUntil(self.skipWaiting())}),self.addEventListener("activate",function(t){t.waitUntil(clients.claim()),console.log("[Service Worker] Сброшен и успешно обновлен до новой версии!")}),self.addEventListener("push",t=>{let i;try{i=t.data?t.data.json():{}}catch{i={}}var n=i.title||"Уведомление",e={body:i.body||"",icon:i.icon||"/icons/icon-192x192.png",badge:i.badge||"/icons/icon-192x192.png",vibrate:[100,50,100],actions:i.actions||[],data:{url:i.url||"/",id:i.id}},o=fetch("https://t.fcdynamo.ru/v1/webpush/delivery?id="+i.id).catch(t=>console.error("Не удалось отправить отчет о доставке:",t));t.waitUntil(Promise.all([o,self.registration.showNotification(n,e)]))}),self.addEventListener("notificationclick",function(n){n.notification.close();var t=fetch("https://t.fcdynamo.ru/v1/webpush/open?id="+n.notification.data?.id).catch(t=>console.error("Не удалось отправить отчет о переходе:",t));let i;"accept"!==n.action&&"decline"!==n.action&&(i=clients.matchAll({type:"window",includeUncontrolled:!0}).then(function(t){for(var i of t)if(i.url===n.notification.data?.url&&"focus"in i)return i.focus();if(clients.openWindow)return clients.openWindow(n.notification.data?.url)})),n.waitUntil(Promise.all([t,i]))}),self.addEventListener("activate",t=>{console.log("[Service Worker] Activate"),t.waitUntil(self.registration.pushManager.getSubscriptions().then(()=>{}))});
|
package/package.json
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fcdm/tracker",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./dist/index.js",
|
|
9
|
+
"./core": "./dist/core/index.js",
|
|
10
|
+
"./tracking": "./dist/modules/tracking/index.js",
|
|
11
|
+
"./notification-popup": "./dist/modules/notification-popup/index.js",
|
|
12
|
+
"./package.json": "./package.json"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "npm run clean && npm run compile",
|
|
16
|
+
"compile": "tsc && node scripts/compile.js",
|
|
17
|
+
"clean": "rm -rf ./dist/",
|
|
18
|
+
"format": "oxfmt src/",
|
|
19
|
+
"lint": "oxlint ."
|
|
20
|
+
},
|
|
21
|
+
"files": ["dist"],
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"typescript": "^6.0.3",
|
|
24
|
+
"uglify-js": "^3.19.3",
|
|
25
|
+
"oxfmt": "^0.28.0",
|
|
26
|
+
"oxlint": "^1.43.0"
|
|
27
|
+
}
|
|
6
28
|
}
|