signalbird 1.4.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.
@@ -0,0 +1,56 @@
1
+ /**
2
+ * signalbird/browser — tarayıcı istemcisi.
3
+ *
4
+ * Ayrı bir giriş noktası olmasının sebebi teknik değil, GÜVENLİKTİR: sunucu
5
+ * anahtarı istemciye gömülemez ve sunucu `Origin` başlığı taşıyan bir istekte
6
+ * onu zaten reddeder. Tarayıcı, açık anahtarı (`sbr_pub_…`) kullanır ve
7
+ * güvenliği gizlilikten değil kısıttan gelir — yalnız izinli alan adlarından
8
+ * ve yalnız izin verilen kanallara yazabilir.
9
+ *
10
+ * React, Vue, Angular ve düz JS aynı istemciyi kullanır; çatıya özel sarmalayıcı
11
+ * yoktur çünkü gereken tek şey bir fonksiyon çağrısıdır.
12
+ */
13
+ type Level = 'debug' | 'info' | 'warn' | 'error' | 'critical';
14
+ interface BrowserConfig {
15
+ /** Açık anahtar (`sbr_pub_…`). Gizli anahtar BURAYA YAZILMAZ. */
16
+ publicKey: string;
17
+ baseUrl?: string;
18
+ source?: string;
19
+ /**
20
+ * Kayıtlar tek tek değil, toplu gider. Kullanıcının tarayıcısından saniyede
21
+ * onlarca istek çıkarmak hem yavaş hem pahalıdır.
22
+ */
23
+ flushIntervalMs?: number;
24
+ maxQueue?: number;
25
+ debug?: boolean;
26
+ }
27
+ declare class SignalbirdBrowser {
28
+ private readonly config;
29
+ private queue;
30
+ private timer;
31
+ private readonly baseUrl;
32
+ private readonly maxQueue;
33
+ constructor(config: BrowserConfig);
34
+ log(channel: string, message: string, level?: Level, context?: Record<string, unknown>): void;
35
+ info(channel: string, message: string, context?: Record<string, unknown>): void;
36
+ warn(channel: string, message: string, context?: Record<string, unknown>): void;
37
+ error(channel: string, message: string, context?: Record<string, unknown>): void;
38
+ /**
39
+ * Tarayıcıdaki yakalanmamış hataları bağlar.
40
+ *
41
+ * Kritik kanala YAZMAZ: istemci tarafı kod herkesin elindedir, oradan
42
+ * kritik alarm tetiklemek kötü niyetli birine ekibin telefonunu çaldırma
43
+ * imkânı verirdi. Sunucu zaten `browser_channels` ile bunu kısıtlar.
44
+ */
45
+ captureErrors(channel?: string): () => void;
46
+ flush(): Promise<void>;
47
+ /** Sayfa kapanırken son gönderim. */
48
+ private flushBeacon;
49
+ destroy(): void;
50
+ }
51
+ /** Uygulama başlangıcında bir kez çağrılır. */
52
+ declare function initSignalbird(config: BrowserConfig): SignalbirdBrowser;
53
+ /** Kurulmamışsa sessizce yok sayar — log çağrısı uygulamayı çökertmemeli. */
54
+ declare function signalbird(): SignalbirdBrowser | null;
55
+
56
+ export { type BrowserConfig, type Level, SignalbirdBrowser, initSignalbird, signalbird };
@@ -0,0 +1,113 @@
1
+ 'use strict';
2
+
3
+ // src/browser/index.ts
4
+ var DEFAULT_BASE_URL = "https://signalbird.io/api";
5
+ var SignalbirdBrowser = class {
6
+ constructor(config) {
7
+ this.config = config;
8
+ this.queue = [];
9
+ this.timer = null;
10
+ if (!config.publicKey?.startsWith("sbr_pub_")) {
11
+ throw new Error(
12
+ "Signalbird: taray\u0131c\u0131 istemcisi a\xE7\u0131k anahtar ister (sbr_pub_\u2026). Gizli anahtar\u0131 (sbr_live_\u2026) istemci koduna KOYMAYIN."
13
+ );
14
+ }
15
+ this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
16
+ this.maxQueue = config.maxQueue ?? 50;
17
+ if (typeof window !== "undefined") {
18
+ this.timer = setInterval(() => void this.flush(), config.flushIntervalMs ?? 3e3);
19
+ window.addEventListener("pagehide", () => this.flushBeacon());
20
+ }
21
+ }
22
+ log(channel, message, level, context) {
23
+ this.queue.push({ channel, message, level, context, source: this.config.source });
24
+ if (this.queue.length >= this.maxQueue) {
25
+ void this.flush();
26
+ }
27
+ }
28
+ info(channel, message, context) {
29
+ this.log(channel, message, "info", context);
30
+ }
31
+ warn(channel, message, context) {
32
+ this.log(channel, message, "warn", context);
33
+ }
34
+ error(channel, message, context) {
35
+ this.log(channel, message, "error", context);
36
+ }
37
+ /**
38
+ * Tarayıcıdaki yakalanmamış hataları bağlar.
39
+ *
40
+ * Kritik kanala YAZMAZ: istemci tarafı kod herkesin elindedir, oradan
41
+ * kritik alarm tetiklemek kötü niyetli birine ekibin telefonunu çaldırma
42
+ * imkânı verirdi. Sunucu zaten `browser_channels` ile bunu kısıtlar.
43
+ */
44
+ captureErrors(channel = "browser") {
45
+ const onError = (event) => {
46
+ this.error(channel, event.message, {
47
+ file: event.filename,
48
+ line: event.lineno,
49
+ column: event.colno,
50
+ url: window.location.href
51
+ });
52
+ };
53
+ const onRejection = (event) => {
54
+ this.error(channel, String(event.reason), { url: window.location.href });
55
+ };
56
+ window.addEventListener("error", onError);
57
+ window.addEventListener("unhandledrejection", onRejection);
58
+ return () => {
59
+ window.removeEventListener("error", onError);
60
+ window.removeEventListener("unhandledrejection", onRejection);
61
+ };
62
+ }
63
+ async flush() {
64
+ if (this.queue.length === 0) return;
65
+ const batch = this.queue.splice(0, 100);
66
+ try {
67
+ await fetch(`${this.baseUrl}/v1/radio/log/batch`, {
68
+ method: "POST",
69
+ headers: {
70
+ "Content-Type": "application/json",
71
+ "X-Signalbird-Key": this.config.publicKey
72
+ },
73
+ body: JSON.stringify({ events: batch }),
74
+ keepalive: true
75
+ });
76
+ } catch (error) {
77
+ if (this.config.debug) {
78
+ console.warn("[signalbird] g\xF6nderilemedi", error);
79
+ }
80
+ this.queue = [...batch, ...this.queue].slice(0, this.maxQueue);
81
+ }
82
+ }
83
+ /** Sayfa kapanırken son gönderim. */
84
+ flushBeacon() {
85
+ if (this.queue.length === 0 || typeof navigator === "undefined") return;
86
+ const blob = new Blob(
87
+ [JSON.stringify({ events: this.queue.splice(0, 100) })],
88
+ { type: "application/json" }
89
+ );
90
+ navigator.sendBeacon?.(
91
+ `${this.baseUrl}/v1/radio/log/batch?key=${encodeURIComponent(this.config.publicKey)}`,
92
+ blob
93
+ );
94
+ }
95
+ destroy() {
96
+ if (this.timer) clearInterval(this.timer);
97
+ void this.flush();
98
+ }
99
+ };
100
+ var singleton = null;
101
+ function initSignalbird(config) {
102
+ singleton = new SignalbirdBrowser(config);
103
+ return singleton;
104
+ }
105
+ function signalbird() {
106
+ return singleton;
107
+ }
108
+
109
+ exports.SignalbirdBrowser = SignalbirdBrowser;
110
+ exports.initSignalbird = initSignalbird;
111
+ exports.signalbird = signalbird;
112
+ //# sourceMappingURL=browser.js.map
113
+ //# sourceMappingURL=browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/browser/index.ts"],"names":[],"mappings":"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","file":"browser.js","sourcesContent":["/**\n * signalbird/browser — tarayıcı istemcisi.\n *\n * Ayrı bir giriş noktası olmasının sebebi teknik değil, GÜVENLİKTİR: sunucu\n * anahtarı istemciye gömülemez ve sunucu `Origin` başlığı taşıyan bir istekte\n * onu zaten reddeder. Tarayıcı, açık anahtarı (`sbr_pub_…`) kullanır ve\n * güvenliği gizlilikten değil kısıttan gelir — yalnız izinli alan adlarından\n * ve yalnız izin verilen kanallara yazabilir.\n *\n * React, Vue, Angular ve düz JS aynı istemciyi kullanır; çatıya özel sarmalayıcı\n * yoktur çünkü gereken tek şey bir fonksiyon çağrısıdır.\n */\nexport type Level = 'debug' | 'info' | 'warn' | 'error' | 'critical';\n\nexport interface BrowserConfig {\n /** Açık anahtar (`sbr_pub_…`). Gizli anahtar BURAYA YAZILMAZ. */\n publicKey: string;\n baseUrl?: string;\n source?: string;\n /**\n * Kayıtlar tek tek değil, toplu gider. Kullanıcının tarayıcısından saniyede\n * onlarca istek çıkarmak hem yavaş hem pahalıdır.\n */\n flushIntervalMs?: number;\n maxQueue?: number;\n debug?: boolean;\n}\n\ninterface QueuedEvent {\n channel: string;\n message: string;\n level?: Level;\n context?: Record<string, unknown>;\n source?: string;\n}\n\nconst DEFAULT_BASE_URL = 'https://signalbird.io/api';\n\nexport class SignalbirdBrowser {\n private queue: QueuedEvent[] = [];\n private timer: ReturnType<typeof setInterval> | null = null;\n private readonly baseUrl: string;\n private readonly maxQueue: number;\n\n constructor(private readonly config: BrowserConfig) {\n if (!config.publicKey?.startsWith('sbr_pub_')) {\n throw new Error(\n 'Signalbird: tarayıcı istemcisi açık anahtar ister (sbr_pub_…). ' +\n 'Gizli anahtarı (sbr_live_…) istemci koduna KOYMAYIN.'\n );\n }\n\n this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, '');\n this.maxQueue = config.maxQueue ?? 50;\n\n if (typeof window !== 'undefined') {\n this.timer = setInterval(() => void this.flush(), config.flushIntervalMs ?? 3000);\n\n // Sekme kapanırken kuyruk boşaltılır. `sendBeacon` sayfa ölürken bile\n // gider; normal fetch yarıda kesilirdi.\n window.addEventListener('pagehide', () => this.flushBeacon());\n }\n }\n\n log(channel: string, message: string, level?: Level, context?: Record<string, unknown>): void {\n this.queue.push({ channel, message, level, context, source: this.config.source });\n\n // Kuyruk dolduysa beklemeden gönder: bellekte sonsuza kadar biriktirmek,\n // logu hiç göndermemekten kötüdür.\n if (this.queue.length >= this.maxQueue) {\n void this.flush();\n }\n }\n\n info(channel: string, message: string, context?: Record<string, unknown>) {\n this.log(channel, message, 'info', context);\n }\n\n warn(channel: string, message: string, context?: Record<string, unknown>) {\n this.log(channel, message, 'warn', context);\n }\n\n error(channel: string, message: string, context?: Record<string, unknown>) {\n this.log(channel, message, 'error', context);\n }\n\n /**\n * Tarayıcıdaki yakalanmamış hataları bağlar.\n *\n * Kritik kanala YAZMAZ: istemci tarafı kod herkesin elindedir, oradan\n * kritik alarm tetiklemek kötü niyetli birine ekibin telefonunu çaldırma\n * imkânı verirdi. Sunucu zaten `browser_channels` ile bunu kısıtlar.\n */\n captureErrors(channel = 'browser'): () => void {\n const onError = (event: ErrorEvent) => {\n this.error(channel, event.message, {\n file: event.filename,\n line: event.lineno,\n column: event.colno,\n url: window.location.href,\n });\n };\n\n const onRejection = (event: PromiseRejectionEvent) => {\n this.error(channel, String(event.reason), { url: window.location.href });\n };\n\n window.addEventListener('error', onError);\n window.addEventListener('unhandledrejection', onRejection);\n\n return () => {\n window.removeEventListener('error', onError);\n window.removeEventListener('unhandledrejection', onRejection);\n };\n }\n\n async flush(): Promise<void> {\n if (this.queue.length === 0) return;\n\n const batch = this.queue.splice(0, 100);\n\n try {\n await fetch(`${this.baseUrl}/v1/radio/log/batch`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-Signalbird-Key': this.config.publicKey,\n },\n body: JSON.stringify({ events: batch }),\n keepalive: true,\n });\n } catch (error) {\n if (this.config.debug) {\n console.warn('[signalbird] gönderilemedi', error);\n }\n // Kayıtlar geri konur ama kuyruk sınırı aşılmaz: ağ uzun süre kapalıysa\n // sekme belleği şişmemeli.\n this.queue = [...batch, ...this.queue].slice(0, this.maxQueue);\n }\n }\n\n /** Sayfa kapanırken son gönderim. */\n private flushBeacon(): void {\n if (this.queue.length === 0 || typeof navigator === 'undefined') return;\n\n const blob = new Blob(\n [JSON.stringify({ events: this.queue.splice(0, 100) })],\n { type: 'application/json' }\n );\n\n // sendBeacon özel başlık taşıyamaz; anahtar sorgu dizesinden gider.\n navigator.sendBeacon?.(\n `${this.baseUrl}/v1/radio/log/batch?key=${encodeURIComponent(this.config.publicKey)}`,\n blob\n );\n }\n\n destroy(): void {\n if (this.timer) clearInterval(this.timer);\n void this.flush();\n }\n}\n\nlet singleton: SignalbirdBrowser | null = null;\n\n/** Uygulama başlangıcında bir kez çağrılır. */\nexport function initSignalbird(config: BrowserConfig): SignalbirdBrowser {\n singleton = new SignalbirdBrowser(config);\n return singleton;\n}\n\n/** Kurulmamışsa sessizce yok sayar — log çağrısı uygulamayı çökertmemeli. */\nexport function signalbird(): SignalbirdBrowser | null {\n return singleton;\n}\n"]}
@@ -0,0 +1,109 @@
1
+ // src/browser/index.ts
2
+ var DEFAULT_BASE_URL = "https://signalbird.io/api";
3
+ var SignalbirdBrowser = class {
4
+ constructor(config) {
5
+ this.config = config;
6
+ this.queue = [];
7
+ this.timer = null;
8
+ if (!config.publicKey?.startsWith("sbr_pub_")) {
9
+ throw new Error(
10
+ "Signalbird: taray\u0131c\u0131 istemcisi a\xE7\u0131k anahtar ister (sbr_pub_\u2026). Gizli anahtar\u0131 (sbr_live_\u2026) istemci koduna KOYMAYIN."
11
+ );
12
+ }
13
+ this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
14
+ this.maxQueue = config.maxQueue ?? 50;
15
+ if (typeof window !== "undefined") {
16
+ this.timer = setInterval(() => void this.flush(), config.flushIntervalMs ?? 3e3);
17
+ window.addEventListener("pagehide", () => this.flushBeacon());
18
+ }
19
+ }
20
+ log(channel, message, level, context) {
21
+ this.queue.push({ channel, message, level, context, source: this.config.source });
22
+ if (this.queue.length >= this.maxQueue) {
23
+ void this.flush();
24
+ }
25
+ }
26
+ info(channel, message, context) {
27
+ this.log(channel, message, "info", context);
28
+ }
29
+ warn(channel, message, context) {
30
+ this.log(channel, message, "warn", context);
31
+ }
32
+ error(channel, message, context) {
33
+ this.log(channel, message, "error", context);
34
+ }
35
+ /**
36
+ * Tarayıcıdaki yakalanmamış hataları bağlar.
37
+ *
38
+ * Kritik kanala YAZMAZ: istemci tarafı kod herkesin elindedir, oradan
39
+ * kritik alarm tetiklemek kötü niyetli birine ekibin telefonunu çaldırma
40
+ * imkânı verirdi. Sunucu zaten `browser_channels` ile bunu kısıtlar.
41
+ */
42
+ captureErrors(channel = "browser") {
43
+ const onError = (event) => {
44
+ this.error(channel, event.message, {
45
+ file: event.filename,
46
+ line: event.lineno,
47
+ column: event.colno,
48
+ url: window.location.href
49
+ });
50
+ };
51
+ const onRejection = (event) => {
52
+ this.error(channel, String(event.reason), { url: window.location.href });
53
+ };
54
+ window.addEventListener("error", onError);
55
+ window.addEventListener("unhandledrejection", onRejection);
56
+ return () => {
57
+ window.removeEventListener("error", onError);
58
+ window.removeEventListener("unhandledrejection", onRejection);
59
+ };
60
+ }
61
+ async flush() {
62
+ if (this.queue.length === 0) return;
63
+ const batch = this.queue.splice(0, 100);
64
+ try {
65
+ await fetch(`${this.baseUrl}/v1/radio/log/batch`, {
66
+ method: "POST",
67
+ headers: {
68
+ "Content-Type": "application/json",
69
+ "X-Signalbird-Key": this.config.publicKey
70
+ },
71
+ body: JSON.stringify({ events: batch }),
72
+ keepalive: true
73
+ });
74
+ } catch (error) {
75
+ if (this.config.debug) {
76
+ console.warn("[signalbird] g\xF6nderilemedi", error);
77
+ }
78
+ this.queue = [...batch, ...this.queue].slice(0, this.maxQueue);
79
+ }
80
+ }
81
+ /** Sayfa kapanırken son gönderim. */
82
+ flushBeacon() {
83
+ if (this.queue.length === 0 || typeof navigator === "undefined") return;
84
+ const blob = new Blob(
85
+ [JSON.stringify({ events: this.queue.splice(0, 100) })],
86
+ { type: "application/json" }
87
+ );
88
+ navigator.sendBeacon?.(
89
+ `${this.baseUrl}/v1/radio/log/batch?key=${encodeURIComponent(this.config.publicKey)}`,
90
+ blob
91
+ );
92
+ }
93
+ destroy() {
94
+ if (this.timer) clearInterval(this.timer);
95
+ void this.flush();
96
+ }
97
+ };
98
+ var singleton = null;
99
+ function initSignalbird(config) {
100
+ singleton = new SignalbirdBrowser(config);
101
+ return singleton;
102
+ }
103
+ function signalbird() {
104
+ return singleton;
105
+ }
106
+
107
+ export { SignalbirdBrowser, initSignalbird, signalbird };
108
+ //# sourceMappingURL=browser.mjs.map
109
+ //# sourceMappingURL=browser.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/browser/index.ts"],"names":[],"mappings":"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","file":"browser.mjs","sourcesContent":["/**\n * signalbird/browser — tarayıcı istemcisi.\n *\n * Ayrı bir giriş noktası olmasının sebebi teknik değil, GÜVENLİKTİR: sunucu\n * anahtarı istemciye gömülemez ve sunucu `Origin` başlığı taşıyan bir istekte\n * onu zaten reddeder. Tarayıcı, açık anahtarı (`sbr_pub_…`) kullanır ve\n * güvenliği gizlilikten değil kısıttan gelir — yalnız izinli alan adlarından\n * ve yalnız izin verilen kanallara yazabilir.\n *\n * React, Vue, Angular ve düz JS aynı istemciyi kullanır; çatıya özel sarmalayıcı\n * yoktur çünkü gereken tek şey bir fonksiyon çağrısıdır.\n */\nexport type Level = 'debug' | 'info' | 'warn' | 'error' | 'critical';\n\nexport interface BrowserConfig {\n /** Açık anahtar (`sbr_pub_…`). Gizli anahtar BURAYA YAZILMAZ. */\n publicKey: string;\n baseUrl?: string;\n source?: string;\n /**\n * Kayıtlar tek tek değil, toplu gider. Kullanıcının tarayıcısından saniyede\n * onlarca istek çıkarmak hem yavaş hem pahalıdır.\n */\n flushIntervalMs?: number;\n maxQueue?: number;\n debug?: boolean;\n}\n\ninterface QueuedEvent {\n channel: string;\n message: string;\n level?: Level;\n context?: Record<string, unknown>;\n source?: string;\n}\n\nconst DEFAULT_BASE_URL = 'https://signalbird.io/api';\n\nexport class SignalbirdBrowser {\n private queue: QueuedEvent[] = [];\n private timer: ReturnType<typeof setInterval> | null = null;\n private readonly baseUrl: string;\n private readonly maxQueue: number;\n\n constructor(private readonly config: BrowserConfig) {\n if (!config.publicKey?.startsWith('sbr_pub_')) {\n throw new Error(\n 'Signalbird: tarayıcı istemcisi açık anahtar ister (sbr_pub_…). ' +\n 'Gizli anahtarı (sbr_live_…) istemci koduna KOYMAYIN.'\n );\n }\n\n this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, '');\n this.maxQueue = config.maxQueue ?? 50;\n\n if (typeof window !== 'undefined') {\n this.timer = setInterval(() => void this.flush(), config.flushIntervalMs ?? 3000);\n\n // Sekme kapanırken kuyruk boşaltılır. `sendBeacon` sayfa ölürken bile\n // gider; normal fetch yarıda kesilirdi.\n window.addEventListener('pagehide', () => this.flushBeacon());\n }\n }\n\n log(channel: string, message: string, level?: Level, context?: Record<string, unknown>): void {\n this.queue.push({ channel, message, level, context, source: this.config.source });\n\n // Kuyruk dolduysa beklemeden gönder: bellekte sonsuza kadar biriktirmek,\n // logu hiç göndermemekten kötüdür.\n if (this.queue.length >= this.maxQueue) {\n void this.flush();\n }\n }\n\n info(channel: string, message: string, context?: Record<string, unknown>) {\n this.log(channel, message, 'info', context);\n }\n\n warn(channel: string, message: string, context?: Record<string, unknown>) {\n this.log(channel, message, 'warn', context);\n }\n\n error(channel: string, message: string, context?: Record<string, unknown>) {\n this.log(channel, message, 'error', context);\n }\n\n /**\n * Tarayıcıdaki yakalanmamış hataları bağlar.\n *\n * Kritik kanala YAZMAZ: istemci tarafı kod herkesin elindedir, oradan\n * kritik alarm tetiklemek kötü niyetli birine ekibin telefonunu çaldırma\n * imkânı verirdi. Sunucu zaten `browser_channels` ile bunu kısıtlar.\n */\n captureErrors(channel = 'browser'): () => void {\n const onError = (event: ErrorEvent) => {\n this.error(channel, event.message, {\n file: event.filename,\n line: event.lineno,\n column: event.colno,\n url: window.location.href,\n });\n };\n\n const onRejection = (event: PromiseRejectionEvent) => {\n this.error(channel, String(event.reason), { url: window.location.href });\n };\n\n window.addEventListener('error', onError);\n window.addEventListener('unhandledrejection', onRejection);\n\n return () => {\n window.removeEventListener('error', onError);\n window.removeEventListener('unhandledrejection', onRejection);\n };\n }\n\n async flush(): Promise<void> {\n if (this.queue.length === 0) return;\n\n const batch = this.queue.splice(0, 100);\n\n try {\n await fetch(`${this.baseUrl}/v1/radio/log/batch`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-Signalbird-Key': this.config.publicKey,\n },\n body: JSON.stringify({ events: batch }),\n keepalive: true,\n });\n } catch (error) {\n if (this.config.debug) {\n console.warn('[signalbird] gönderilemedi', error);\n }\n // Kayıtlar geri konur ama kuyruk sınırı aşılmaz: ağ uzun süre kapalıysa\n // sekme belleği şişmemeli.\n this.queue = [...batch, ...this.queue].slice(0, this.maxQueue);\n }\n }\n\n /** Sayfa kapanırken son gönderim. */\n private flushBeacon(): void {\n if (this.queue.length === 0 || typeof navigator === 'undefined') return;\n\n const blob = new Blob(\n [JSON.stringify({ events: this.queue.splice(0, 100) })],\n { type: 'application/json' }\n );\n\n // sendBeacon özel başlık taşıyamaz; anahtar sorgu dizesinden gider.\n navigator.sendBeacon?.(\n `${this.baseUrl}/v1/radio/log/batch?key=${encodeURIComponent(this.config.publicKey)}`,\n blob\n );\n }\n\n destroy(): void {\n if (this.timer) clearInterval(this.timer);\n void this.flush();\n }\n}\n\nlet singleton: SignalbirdBrowser | null = null;\n\n/** Uygulama başlangıcında bir kez çağrılır. */\nexport function initSignalbird(config: BrowserConfig): SignalbirdBrowser {\n singleton = new SignalbirdBrowser(config);\n return singleton;\n}\n\n/** Kurulmamışsa sessizce yok sayar — log çağrısı uygulamayı çökertmemeli. */\nexport function signalbird(): SignalbirdBrowser | null {\n return singleton;\n}\n"]}