signalbird 2.5.0 → 2.8.1

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.
Files changed (51) hide show
  1. package/README.md +78 -36
  2. package/dist/angular.d.mts +4 -4
  3. package/dist/angular.d.ts +4 -4
  4. package/dist/angular.js +77 -19
  5. package/dist/angular.js.map +1 -1
  6. package/dist/angular.mjs +77 -19
  7. package/dist/angular.mjs.map +1 -1
  8. package/dist/app.d.mts +78 -53
  9. package/dist/app.d.ts +78 -53
  10. package/dist/app.js +76 -18
  11. package/dist/app.js.map +1 -1
  12. package/dist/app.mjs +76 -18
  13. package/dist/app.mjs.map +1 -1
  14. package/dist/browser.d.mts +1 -13
  15. package/dist/browser.d.ts +1 -13
  16. package/dist/browser.js +25 -3
  17. package/dist/browser.js.map +1 -1
  18. package/dist/browser.mjs +25 -3
  19. package/dist/browser.mjs.map +1 -1
  20. package/dist/embed.d.mts +6 -6
  21. package/dist/embed.d.ts +6 -6
  22. package/dist/embed.js +1 -1
  23. package/dist/embed.js.map +1 -1
  24. package/dist/embed.mjs +1 -1
  25. package/dist/embed.mjs.map +1 -1
  26. package/dist/index.d.mts +61 -50
  27. package/dist/index.d.ts +61 -50
  28. package/dist/index.js +61 -23
  29. package/dist/index.js.map +1 -1
  30. package/dist/index.mjs +62 -24
  31. package/dist/index.mjs.map +1 -1
  32. package/dist/react-native.d.mts +2 -2
  33. package/dist/react-native.d.ts +2 -2
  34. package/dist/react-native.js +76 -18
  35. package/dist/react-native.js.map +1 -1
  36. package/dist/react-native.mjs +76 -18
  37. package/dist/react-native.mjs.map +1 -1
  38. package/dist/react.d.mts +4 -4
  39. package/dist/react.d.ts +4 -4
  40. package/dist/react.js +76 -18
  41. package/dist/react.js.map +1 -1
  42. package/dist/react.mjs +76 -18
  43. package/dist/react.mjs.map +1 -1
  44. package/dist/signalbird.js +20 -20
  45. package/dist/vue.d.mts +3 -3
  46. package/dist/vue.d.ts +3 -3
  47. package/dist/vue.js +76 -18
  48. package/dist/vue.js.map +1 -1
  49. package/dist/vue.mjs +76 -18
  50. package/dist/vue.mjs.map +1 -1
  51. package/package.json +3 -2
package/README.md CHANGED
@@ -19,17 +19,17 @@ Bir seçenek daha var ve kod yazmaz: hazır sohbet widget'ı
19
19
  | Dil / platform | Telsiz | Gönderim | Yönetim | Uygulama | Kurulum |
20
20
  |---|:--:|:--:|:--:|:--:|---|
21
21
  | Node.js / TypeScript | ✓ | ✓ | ✓ | ✓ | `npm i signalbird` |
22
- | Tarayıcı (düz JS) | ✓ | — | — | ✓ | `signalbird/browser` · `/app` |
22
+ | Tarayıcı (düz JS) | ✓ | - | - | ✓ | `signalbird/browser` · `/app` |
23
23
  | React / Next.js | ✓ | ✓ | ✓ | ✓ | `signalbird/react` |
24
- | Vue 3 | ✓ | — | — | ✓ | `signalbird/vue` |
25
- | Angular | ✓ | — | — | ✓ | `signalbird/angular` |
26
- | React Native / Expo | ✓ | — | — | ✓ | `signalbird/react-native` |
27
- | PHP / Laravel | ✓ | ✓ | ✓ | — | `composer require pariette/signalbird` |
28
- | Python | ✓ | ✓ | ✓ | — | `pip install signalbird` |
29
- | Go | ✓ | ✓ | ✓ | — | `go get github.com/Pariette-Inc/signalbird.sdk` |
30
- | .NET / ASP.NET Core | ✓ | ✓ | ✓ | — | `dotnet add package Signalbird.Sdk` |
31
- | Swift (iOS) | ✓ | — | — | ✓ | SPM: `Signalbird` |
32
- | Kotlin (Android) | ✓ | — | — | ✓ | `io.signalbird:signalbird-sdk` |
24
+ | Vue 3 | ✓ | - | - | ✓ | `signalbird/vue` |
25
+ | Angular | ✓ | - | - | ✓ | `signalbird/angular` |
26
+ | React Native / Expo | ✓ | - | - | ✓ | `signalbird/react-native` |
27
+ | PHP / Laravel | ✓ | ✓ | ✓ | - | `composer require pariette/signalbird` |
28
+ | Python | ✓ | ✓ | ✓ | - | `pip install signalbird` |
29
+ | Go | ✓ | ✓ | ✓ | - | `go get github.com/Pariette-Inc/signalbird.sdk` |
30
+ | .NET / ASP.NET Core | ✓ | ✓ | ✓ | - | `dotnet add package Signalbird.Sdk` |
31
+ | Swift (iOS) | ✓ | - | - | ✓ | SPM: `Signalbird` |
32
+ | Kotlin (Android) | ✓ | - | - | ✓ | `io.signalbird:signalbird-sdk` |
33
33
 
34
34
  Metot adları diller arasında **birebir** aynıdır; her dil kendi yazım
35
35
  geleneğini korur (`createRadioProject` / `create_radio_project` /
@@ -65,7 +65,7 @@ SIGNALBIRD_DOMAIN_KEY=sb_secret_live_…
65
65
  Takım anahtarı (`sb_…`) her şeyi kapsar: e-posta, SMS, push, kişi ve kampanya,
66
66
  sohbet, uygulama, domain, Telsiz log yazımı. Ne yapabileceğini anahtarın
67
67
  kapsamları belirler; panelde üretilirken seçersiniz. **Adres yazmanız
68
- gerekmez** — üretim kökü paketin içindedir.
68
+ gerekmez** - üretim kökü paketin içindedir.
69
69
 
70
70
  Ayrı anahtar isteğe bağlıdır: bir sunucunun yalnız log yazıp gönderim
71
71
  yapamamasını istiyorsanız o sunucuya dar kapsamlı ikinci bir anahtar verirsiniz.
@@ -80,7 +80,7 @@ ve gizli olmadıkları için ayrı durmak zorundadırlar:
80
80
 
81
81
  Gizli anahtar tarayıcıya **gömülemez**: sunucu, `Origin` başlığı taşıyan bir
82
82
  istekte gizli anahtarı reddeder (`SECRET_KEY_IN_BROWSER`). Bu bir kolaylık
83
- değil, kasıtlı bir duvardır — anahtar bir kez istemciye indiğinde herkesindir.
83
+ değil, kasıtlı bir duvardır - anahtar bir kez istemciye indiğinde herkesindir.
84
84
 
85
85
  ## Kurulum
86
86
 
@@ -94,10 +94,10 @@ değil, kasıtlı bir duvardır — anahtar bir kez istemciye indiğinde herkesi
94
94
  | Go | `go get github.com/Pariette-Inc/signalbird.sdk` |
95
95
  | .NET, ASP.NET Core | `dotnet add package Signalbird.Sdk` |
96
96
  | Swift (iOS, macOS) | SPM: `https://github.com/Pariette-Inc/signalbird.sdk` |
97
- | Kotlin (Android) | `implementation("io.signalbird:signalbird-sdk:2.5.0")` |
97
+ | Kotlin (Android) | `implementation("io.signalbird:signalbird-sdk:2.8.1")` |
98
98
  | Canlı sohbet widget'ı (herhangi bir site) | `<script async src="https://signalbird.io/sdk/v1/signalbird.js" data-key="sb_public_live_…" data-channel="destek"></script>` |
99
99
 
100
- > Hepsi **bu repodan** çıkar ve **aynı sürümü** taşır — ayrı SDK reposu ya da
100
+ > Hepsi **bu repodan** çıkar ve **aynı sürümü** taşır - ayrı SDK reposu ya da
101
101
  > dil başına sürüm yoktur.
102
102
 
103
103
  ## Node.js / TypeScript
@@ -146,7 +146,7 @@ const result = await signalbird().batch([
146
146
  ### Next.js
147
147
 
148
148
  Sunucu tarafında (route handler, server action, `app/api/**`) doğrudan
149
- `signalbird` kullanılır. **İstemci bileşenlerinde kullanmayın** — anahtar
149
+ `signalbird` kullanılır. **İstemci bileşenlerinde kullanmayın** - anahtar
150
150
  paketle birlikte tarayıcıya iner.
151
151
 
152
152
  ```ts
@@ -183,7 +183,7 @@ sb.error('browser', 'sepet güncellenemedi', { cartId })
183
183
  Kayıtlar tek tek değil, **toplu** gider (varsayılan 3 saniyede bir) ve sekme
184
184
  kapanırken `sendBeacon` ile boşaltılır.
185
185
 
186
- **React** — `app/providers.tsx` ya da `main.tsx`:
186
+ **React** - `app/providers.tsx` ya da `main.tsx`:
187
187
 
188
188
  ```tsx
189
189
  useEffect(() => {
@@ -192,14 +192,14 @@ useEffect(() => {
192
192
  }, [])
193
193
  ```
194
194
 
195
- **Vue** — `main.ts`:
195
+ **Vue** - `main.ts`:
196
196
 
197
197
  ```ts
198
198
  const sb = initSignalbird({ publicKey: import.meta.env.VITE_SIGNALBIRD_DOMAIN_KEY })
199
199
  app.config.errorHandler = (err) => sb.error('browser', String(err))
200
200
  ```
201
201
 
202
- **Angular** — `ErrorHandler` sağlayıcısı:
202
+ **Angular** - `ErrorHandler` sağlayıcısı:
203
203
 
204
204
  ```ts
205
205
  @Injectable()
@@ -232,7 +232,7 @@ SIGNALBIRD_DOMAIN_KEY=sb_secret_live_…
232
232
  SIGNALBIRD_SOURCE=api-01
233
233
  ```
234
234
 
235
- **Laravel'in kendi loglarını Telsiz'e bağlamak** — `config/logging.php`:
235
+ **Laravel'in kendi loglarını Telsiz'e bağlamak** - `config/logging.php`:
236
236
 
237
237
  ```php
238
238
  'signalbird' => [
@@ -262,17 +262,26 @@ Signalbird::error('api', 'veritabanı bağlantısı koptu');
262
262
  geçerli bir sebep değildir. Geliştirme sırasında `throwOnError: true`.
263
263
  - **Tanımsız kanal düşürülmez.** İlk `log('odeme-hatasi', …)` çağrısında kanal
264
264
  kendiliğinden açılır ve panelde "otomatik açıldı" işaretiyle görünür. Yeni
265
- kanal **sessizdir** — kuralı ekip koyar.
265
+ kanal **sessizdir** - kuralı ekip koyar.
266
266
  - **Seviye kanalın varsayılanını ezer.** `level` göndermezseniz kanalın kendi
267
267
  seviyesi geçerlidir.
268
268
  - **Kritik seviye sessiz saatleri deler.** Gece üçte ölen servis sabahı bekleyemez.
269
269
  - **Tekrar bastırma kaydı değil bildirimi susturur.** Aynı mesaj kanalın
270
270
  `dedupe` süresi içinde tekrar gelirse ikinci bildirim gitmez ama kayıt tutulur.
271
+ - **SDK sürümünü bildirir (2.6.0).** Her istek `X-Signalbird-Sdk:
272
+ <platform>/<sürüm>` taşır. Yeni sürüm çıktığında SDK süreç başına **bir kez**
273
+ uyarı yazar; eski sürümdeki anahtarların sahiplerine panel bildirimi gider.
274
+ Uyarı hata değildir, istek sonucu değişmez (CONTRACT §14).
275
+ - **Ziyaretçi kimliği doğrulanır (2.7.0).** Sohbet/push ziyaretçisinin
276
+ `external_id`'si ancak sunucunuzda üretilen `identity_hash` ile birlikte
277
+ gelirse güvenilir sayılır: kişi kaydına bağlama, cihaza push ve kanal
278
+ ajanının araçlarına giden güvenilir kimlik buna bağlıdır. Widget ayrıca
279
+ gerektiğinde Cloudflare Turnstile ister (CONTRACT §15).
271
280
 
272
281
  ## Gönderim (Messaging)
273
282
 
274
283
  Takım API anahtarı (`sb_…`, panelde **Konsol → API anahtarları**, scope'lu)
275
- ile çalışır. Telsiz anahtarı burada geçmez — istemci kurulurken
284
+ ile çalışır. Telsiz anahtarı burada geçmez - istemci kurulurken
276
285
  `WRONG_KEY_TYPE` ile reddeder. Yalnız sunucuda kullanılır.
277
286
 
278
287
  Node:
@@ -370,7 +379,7 @@ import { management } from 'signalbird'
370
379
  // Yeni ortam kurulumu: proje aç, kanalını tanımla, anahtarı sakla
371
380
  const { data } = await management().createRadioProject({ name: 'ödeme-servisi' })
372
381
 
373
- // `secret` YALNIZ burada döner — sunucuda yalnız özeti saklanır
382
+ // `secret` YALNIZ burada döner - sunucuda yalnız özeti saklanır
374
383
  await vault.write('SIGNALBIRD_DOMAIN_KEY', data!.secret)
375
384
 
376
385
  await management().createRadioChannel(data!.project.id, {
@@ -405,7 +414,7 @@ const { data } = await management().embedToken({ module: 'chat' })
405
414
  // data.url → 120 saniyelik, TEK KULLANIMLIK adres; doğrudan <iframe>'e verin
406
415
  ```
407
416
 
408
- Anahtar `embed:issue` kapsamı ister — jeton 60 dakikalık bir panel oturumuna
417
+ Anahtar `embed:issue` kapsamı ister - jeton 60 dakikalık bir panel oturumuna
409
418
  çevrildiği için bu kapsam bilerek ayrıdır.
410
419
 
411
420
  Aynısı PHP, Python, Go ve .NET'te birebir aynı metot adlarıyla:
@@ -428,7 +437,7 @@ await management.CreateRadioProjectAsync(new { name = "ödeme-servisi" });
428
437
 
429
438
  Tam liste (40 metot): `docs/CONTRACT.md § 10`.
430
439
 
431
- ## Partner — müşteri sağlama
440
+ ## Partner - müşteri sağlama
432
441
 
433
442
  Signalbird'ü kendi ürününüzün içinde satıyorsanız (sözleşmeli platform) bu yüzey
434
443
  sizindir: müşteri hesabı açar, domain ekleyip izlemeye alır, uptime okur, ödeme
@@ -439,7 +448,7 @@ import { SignalbirdPartner } from 'signalbird'
439
448
 
440
449
  const partner = new SignalbirdPartner({ domainKey: process.env.SIGNALBIRD_DOMAIN_KEY! })
441
450
 
442
- // Müşteri açıldı — idempotent: aynı external_id ikinci kez yeni hesap AÇMAZ
451
+ // Müşteri açıldı - idempotent: aynı external_id ikinci kez yeni hesap AÇMAZ
443
452
  const { data } = await partner.createCompany({
444
453
  external_id: 'sc_9911',
445
454
  name: 'Acme',
@@ -465,7 +474,7 @@ const embed = await partner.createEmbedToken('sc_9911', {
465
474
  })
466
475
  // → <iframe src={embed.data.url} />
467
476
 
468
- // Gönderilen her şeyin durumu — kendi panelinizde çizmek için (salt okur)
477
+ // Gönderilen her şeyin durumu - kendi panelinizde çizmek için (salt okur)
469
478
  const log = await partner.listMessages('sc_9911', { channel: 'email', status: 'delivered' })
470
479
  const one = await partner.getMessage('sc_9911', 'm_01J…') // olay zaman çizelgesi
471
480
  const sum = await partner.messageSummary('sc_9911', '7d') // kanal bazlı özet
@@ -479,10 +488,10 @@ PHP'de `Signalbird::partner()->createCompany([...])`.
479
488
  İki kural: **anahtar tarayıcıya inmez** ve **TXT'siz domain kampanya
480
489
  gönderemez** (izleme, sohbet ve push açıktır). Ayrıntı: `docs/CONTRACT.md § 12`.
481
490
 
482
- Mesaj uçları **salt okurdur**: alıcı maskeli döner, gövde hiç dönmez — gövde
491
+ Mesaj uçları **salt okurdur**: alıcı maskeli döner, gövde hiç dönmez - gövde
483
492
  zaten saklanmıyor.
484
493
 
485
- ## Gönderim — müşterinin kendi sisteminden
494
+ ## Gönderim - müşterinin kendi sisteminden
486
495
 
487
496
  Bir platformun (SubmitCMS, veribenim…) müşterisiyseniz kendi sunucunuzdan da
488
497
  gönderim yapabilirsiniz. Gereken tek şey **kendi takım anahtarınızdır**
@@ -537,7 +546,7 @@ istemiyorsanız ikincisi.
537
546
  3. **Anahtar sunucuda kalır.** Tarayıcıya ya da mobil uygulamaya konmaz;
538
547
  oralar için açık uygulama anahtarı (`sb_public_live_…`) vardır.
539
548
 
540
- ## Uygulama (App) — kendi sohbet arayüzünüz
549
+ ## Uygulama (App) - kendi sohbet arayüzünüz
541
550
 
542
551
  Hazır widget yerine kendi arayüzünüzü yazmak, ya da sohbeti **mobil
543
552
  uygulamanıza** koymak istiyorsanız bu yüzey içindir. Açık uygulama anahtarı
@@ -638,17 +647,49 @@ Signalbird.chat.open() // close() · toggle() · isOpen()
638
647
  Signalbird.chat.on('unread', (n) => badge.textContent = n)
639
648
  Signalbird.push.register({ token, platform: 'web', provider: 'fcm' })
640
649
  Signalbird.destroy()
650
+ Signalbird.reset() // kullanıcı ÇIKIŞ yapınca (2.8.0, CONTRACT §15.3)
641
651
  ```
642
652
 
653
+ **Çıkışta `Signalbird.reset()` zorunlu (2.8.0).** Ziyaretçi sırrını, sohbet
654
+ geçmişini ve kimliği tarayıcıdan siler; aynı tarayıcıyı kullanan sonraki kişi
655
+ öncekinin sohbetini görmez, destek ajanı onu önceki kullanıcı sanmaz.
656
+ Sunucu da kimliği her oturumda yeniden imza ister.
657
+
643
658
  `data-key`/`data-channel` yerine `Signalbird.init({ publicKey, chatKey, baseUrl?, locale? })` da
644
- çağrılabilir. Widget ev sahibi sayfaya asla hata fırlatmaz; Shadow DOM içinde
645
- çalışır, sayfanızın CSS'iyle çakışmaz; < 20 KB gzip. Ayrıntı:
659
+ çağrılabilir.
660
+
661
+ **Kimlik doğrulaması (2.7.0).** Oturum açmış kullanıcıyı tanıtırken
662
+ `external_id`'nin hash'ini SUNUCUNUZDA üretip birlikte verin; yoksa Signalbird
663
+ bu kimliği doğrulanmamış sayar (kişi kaydına bağlamaz, cihaza push hedeflemez,
664
+ ajan araçları kimliği yalnız `visitor.unverified` altında görür):
665
+
666
+ ```php
667
+ // Blade - gizli anahtar sunucuda kalır
668
+ <script async src="https://signalbird.io/sdk/v1/signalbird.js"
669
+ data-key="sb_public_live_…" data-channel="destek"
670
+ data-external-id="{{ $user->id }}"
671
+ data-identity-hash="{{ Signalbird::identityHash((string) $user->id) }}"></script>
672
+ ```
673
+
674
+ ```js
675
+ Signalbird.identify({ external_id: 'user-1042', identityHash: '<sunucudan>', email: 'ali@example.com' })
676
+ ```
677
+
678
+ `identity_hash = hex(HMAC-SHA256(hex(SHA-256(sb_secret_live_…)), external_id))`;
679
+ Node `client.identityHash(id)`, Python `client.identity_hash(id)`, Go
680
+ `client.IdentityHash(id)`, .NET `client.IdentityHash(id)`.
681
+
682
+ **Captcha.** Kanalda açıksa widget yeni ziyaretçide ve doğrulanmamış
683
+ ziyaretçinin ilk konuşmasında görünmez bir Cloudflare Turnstile jetonu alır;
684
+ betik yalnız panel ilk açıldığında yüklenir, etkileşim yalnız Cloudflare
685
+ isterse görünür. Kurulumda yapmanız gereken bir şey yoktur. Widget ev sahibi sayfaya asla hata fırlatmaz; Shadow DOM içinde
686
+ çalışır, sayfanızın CSS'iyle çakışmaz; < 40 KB gzip. Ayrıntı:
646
687
  `docs/CONTRACT.md § 9` ve https://signalbird.io/sdk/widget.
647
688
 
648
- ## Gömme (embed) — Signalbird ekranını kendi panelinizde çalıştırın
689
+ ## Gömme (embed) - Signalbird ekranını kendi panelinizde çalıştırın
649
690
 
650
691
  Partner (veribenim, submitcms, yeni ortaklar) Signalbird modülünü kendi
651
- panelinin içinde gösterir. Ekran kopyalanmaz — **çalışan ekranın kendisi**
692
+ panelinin içinde gösterir. Ekran kopyalanmaz - **çalışan ekranın kendisi**
652
693
  gelir; Signalbird'de güncellenen her şey partner panelinde de anında günceldir.
653
694
 
654
695
  ```html
@@ -693,7 +734,7 @@ Sunucu tarafı tek çağrıdır (`Signalbird::partner()->createEmbedToken(...)`,
693
734
  | `CHANNEL_NOT_ALLOWED` | Tarayıcı anahtarı bu kanala yazamaz |
694
735
  | `MODULE_DISABLED` | Paketinizde Telsiz (`logger`) modülü yok |
695
736
  | `LIMIT_REACHED` | Aylık kayıt limitiniz doldu |
696
- | `CHANNEL_DISABLED` | Kanal kapalı — kayıt yazılmaz, kota da harcanmaz |
737
+ | `CHANNEL_DISABLED` | Kanal kapalı - kayıt yazılmaz, kota da harcanmaz |
697
738
 
698
739
  Gönderim ve Yönetim istemcilerine özgü: `WRONG_KEY_TYPE` (kurulumda),
699
740
  `API_KEY_INVALID`, `API_KEY_SCOPE` (anahtarda gereken scope yok),
@@ -701,5 +742,6 @@ Gönderim ve Yönetim istemcilerine özgü: `WRONG_KEY_TYPE` (kurulumda),
701
742
  `LIST_NOT_FOUND`, `MODULE_DISABLED`, `NETWORK_ERROR`, `TIMEOUT`, `HTTP_<durum>`.
702
743
 
703
744
  Uygulama yüzeyi ve widget: `VISITOR_INVALID` (yerel kimlik silinir, yeni
704
- oturum açılır), `CHAT_UNAVAILABLE` (kota — "sohbet kullanılamıyor" bandı),
705
- `NOT_INITIALIZED`.
745
+ oturum açılır), `CHAT_UNAVAILABLE` (kota - "sohbet kullanılamıyor" bandı),
746
+ `NOT_INITIALIZED`, `CAPTCHA_REQUIRED` / `CAPTCHA_INVALID` (403 - widget yeni
747
+ jetonla bir kez yeniden dener), `CONVERSATION_RATE_LIMITED` (429 - nazik bant).
@@ -2,7 +2,7 @@ import { Observable } from 'rxjs';
2
2
  import { SignalbirdApp, AppConfig, ChatState, SessionInput, IdentifyInput, RegisterDeviceInput } from './app.mjs';
3
3
 
4
4
  /**
5
- * `signalbird/angular` — Angular servisi ve sağlayıcısı.
5
+ * `signalbird/angular` - Angular servisi ve sağlayıcısı.
6
6
  *
7
7
  * Dekoratör KULLANMAZ: `@Injectable()` yazsaydık paketin derlenmesi Angular
8
8
  * sürümüne bağlanırdı ve her büyük sürümde yeniden yayın gerekirdi. Bunun
@@ -12,7 +12,7 @@ import { SignalbirdApp, AppConfig, ChatState, SessionInput, IdentifyInput, Regis
12
12
  * RxJS bir `peerDependency`'dir (Angular zaten getirir).
13
13
  */
14
14
 
15
- /** DI belirteci — Angular'ın `InjectionToken`'ına sarılır (aşağıda). */
15
+ /** DI belirteci - Angular'ın `InjectionToken`'ına sarılır (aşağıda). */
16
16
  declare const SIGNALBIRD_CONFIG = "SIGNALBIRD_CONFIG";
17
17
  /**
18
18
  * Uygulama başına tek örnek (`providedIn: 'root'` karşılığı).
@@ -27,7 +27,7 @@ declare class SignalbirdService {
27
27
  constructor(config: AppConfig);
28
28
  /** Sohbet durumu akışı. İlk abone olduğunda oturum başlar. */
29
29
  chat$(): Observable<ChatState>;
30
- /** Panel açık mı — yoklama hızını belirler. */
30
+ /** Panel açık mı - yoklama hızını belirler. */
31
31
  setOpen(open: boolean): void;
32
32
  send(body: string, attachments?: unknown[]): Promise<unknown>;
33
33
  typing(isTyping: boolean): void;
@@ -48,7 +48,7 @@ declare class SignalbirdService {
48
48
  /**
49
49
  * `bootstrapApplication(App, { providers: [provideSignalbird({ publicKey })] })`
50
50
  *
51
- * Dönen nesne Angular'ın `Provider` biçimindedir ama tipi buraya gömülmez —
51
+ * Dönen nesne Angular'ın `Provider` biçimindedir ama tipi buraya gömülmez -
52
52
  * paket Angular'a derleme zamanı bağımlılık taşımaz.
53
53
  */
54
54
  declare function provideSignalbird(config: AppConfig): {
package/dist/angular.d.ts CHANGED
@@ -2,7 +2,7 @@ import { Observable } from 'rxjs';
2
2
  import { SignalbirdApp, AppConfig, ChatState, SessionInput, IdentifyInput, RegisterDeviceInput } from './app.js';
3
3
 
4
4
  /**
5
- * `signalbird/angular` — Angular servisi ve sağlayıcısı.
5
+ * `signalbird/angular` - Angular servisi ve sağlayıcısı.
6
6
  *
7
7
  * Dekoratör KULLANMAZ: `@Injectable()` yazsaydık paketin derlenmesi Angular
8
8
  * sürümüne bağlanırdı ve her büyük sürümde yeniden yayın gerekirdi. Bunun
@@ -12,7 +12,7 @@ import { SignalbirdApp, AppConfig, ChatState, SessionInput, IdentifyInput, Regis
12
12
  * RxJS bir `peerDependency`'dir (Angular zaten getirir).
13
13
  */
14
14
 
15
- /** DI belirteci — Angular'ın `InjectionToken`'ına sarılır (aşağıda). */
15
+ /** DI belirteci - Angular'ın `InjectionToken`'ına sarılır (aşağıda). */
16
16
  declare const SIGNALBIRD_CONFIG = "SIGNALBIRD_CONFIG";
17
17
  /**
18
18
  * Uygulama başına tek örnek (`providedIn: 'root'` karşılığı).
@@ -27,7 +27,7 @@ declare class SignalbirdService {
27
27
  constructor(config: AppConfig);
28
28
  /** Sohbet durumu akışı. İlk abone olduğunda oturum başlar. */
29
29
  chat$(): Observable<ChatState>;
30
- /** Panel açık mı — yoklama hızını belirler. */
30
+ /** Panel açık mı - yoklama hızını belirler. */
31
31
  setOpen(open: boolean): void;
32
32
  send(body: string, attachments?: unknown[]): Promise<unknown>;
33
33
  typing(isTyping: boolean): void;
@@ -48,7 +48,7 @@ declare class SignalbirdService {
48
48
  /**
49
49
  * `bootstrapApplication(App, { providers: [provideSignalbird({ publicKey })] })`
50
50
  *
51
- * Dönen nesne Angular'ın `Provider` biçimindedir ama tipi buraya gömülmez —
51
+ * Dönen nesne Angular'ın `Provider` biçimindedir ama tipi buraya gömülmez -
52
52
  * paket Angular'a derleme zamanı bağımlılık taşımaz.
53
53
  */
54
54
  declare function provideSignalbird(config: AppConfig): {
package/dist/angular.js CHANGED
@@ -4,6 +4,26 @@ var rxjs = require('rxjs');
4
4
 
5
5
  // src/angular/index.ts
6
6
 
7
+ // src/shared/version.ts
8
+ var SDK_VERSION = "2.8.1" ;
9
+ var SDK_HEADER = "X-Signalbird-Sdk";
10
+ function sdkHeaderValue(platform) {
11
+ return `${platform}/${SDK_VERSION}`;
12
+ }
13
+ var warned = false;
14
+ function noteSdkStatus(headers) {
15
+ if (warned || !headers) return;
16
+ try {
17
+ const status = headers.get("Signalbird-Sdk-Status");
18
+ if (status !== "outdated" && status !== "unsupported") return;
19
+ warned = true;
20
+ const latest = headers.get("Signalbird-Sdk-Latest") ?? "?";
21
+ const message = status === "unsupported" ? `[signalbird] Bu SDK s\xFCr\xFCm\xFC (${SDK_VERSION}) art\u0131k desteklenmiyor. Son s\xFCr\xFCm: ${latest}. Paketi g\xFCncelleyin.` : `[signalbird] Yeni SDK s\xFCr\xFCm\xFC var: ${latest} (kurulu: ${SDK_VERSION}).`;
22
+ if (typeof console !== "undefined") console.warn(message);
23
+ } catch {
24
+ }
25
+ }
26
+
7
27
  // src/app/client.ts
8
28
  var DEFAULT_BASE_URL = "https://live.signalbird.io/api";
9
29
  var STORAGE_KEY = "sb_visitor";
@@ -53,9 +73,19 @@ var SignalbirdApp = class {
53
73
  this.doFetch = config.fetchImpl ?? ((...args) => fetch(...args));
54
74
  }
55
75
  // ── Kimlik ────────────────────────────────────────────────────────────
56
- /** Uygulama ayarları: sohbet açık mı, renk, çalışma saati, ön-form. */
57
- bootstrap() {
58
- return this.request("POST", "/v1/sdk/bootstrap", { locale: this.config.locale });
76
+ /**
77
+ * Uygulama ayarları: sohbet açık mı, renk, çalışma saati, ön-form.
78
+ *
79
+ * Oturum açmış kullanıcı biliniyorsa `external_id` + `identity_hash`
80
+ * verilir (CONTRACT §15.3): sunucu kimliği açılışta da doğrular. Kimliksiz
81
+ * açılış ziyaretçiye dokunmaz; çıkışta `signOut()` çağrılır.
82
+ */
83
+ bootstrap(identity) {
84
+ const id = identity ? withIdentityHash(identity) : void 0;
85
+ return this.request("POST", "/v1/sdk/bootstrap", {
86
+ locale: this.config.locale,
87
+ ...id?.external_id && id.identity_hash ? { external_id: id.external_id, identity_hash: id.identity_hash } : {}
88
+ });
59
89
  }
60
90
  /**
61
91
  * Canlı bağlantı kanalı için imza.
@@ -74,7 +104,7 @@ var SignalbirdApp = class {
74
104
  * derse yerel kimlik silinir ve bir sonraki çağrı yeni oturum açar.
75
105
  */
76
106
  async startSession(input = {}) {
77
- const result = await this.request("POST", "/v1/sdk/chat/session", input);
107
+ const result = await this.request("POST", "/v1/sdk/chat/session", withIdentityHash(input));
78
108
  const visitor = result.data?.visitor;
79
109
  if (result.ok && visitor?.id && visitor.secret) {
80
110
  await this.storeVisitor({
@@ -89,9 +119,9 @@ var SignalbirdApp = class {
89
119
  }
90
120
  /** Oturum açmış kullanıcıyı ziyaretçiye bağlar (kişi kaydı upsert edilir). */
91
121
  identify(input) {
92
- return this.request("POST", "/v1/sdk/identify", input);
122
+ return this.request("POST", "/v1/sdk/identify", withIdentityHash(input));
93
123
  }
94
- /** Saklanan ziyaretçi kimliği — yoksa `null`. */
124
+ /** Saklanan ziyaretçi kimliği - yoksa `null`. */
95
125
  async currentVisitor() {
96
126
  const stored = await this.loadVisitor();
97
127
  return stored ? { id: stored.id, name: stored.name, email: stored.email } : null;
@@ -110,7 +140,7 @@ var SignalbirdApp = class {
110
140
  return this.request("GET", `/v1/sdk/chat/conversations/${enc(id)}`, void 0, query);
111
141
  }
112
142
  /**
113
- * İlk mesajla konuşma açar. Kota burada harcanır — konuşma başına sayılır,
143
+ * İlk mesajla konuşma açar. Kota burada harcanır - konuşma başına sayılır,
114
144
  * mesaj başına değil.
115
145
  */
116
146
  startConversation(input) {
@@ -184,25 +214,25 @@ var SignalbirdApp = class {
184
214
  // ── Push ──────────────────────────────────────────────────────────────
185
215
  /**
186
216
  * Cihaz token'ını kaydeder. Token'ı almak (FCM/APNs/Web Push izni) ev
187
- * sahibinin işidir; SDK yalnız iletir — izin diyaloğunu kimin, ne zaman
217
+ * sahibinin işidir; SDK yalnız iletir - izin diyaloğunu kimin, ne zaman
188
218
  * göstereceği ürün kararıdır, kütüphane kararı değil.
189
219
  */
190
220
  registerDevice(input) {
191
- return this.request("POST", "/v1/sdk/devices", input);
221
+ return this.request("POST", "/v1/sdk/devices", withIdentityHash(input));
192
222
  }
193
223
  /** Çıkışta çağrılır: kayıt silinmez, kapatılır (geçmiş korunur). */
194
224
  unregisterDevice(token) {
195
225
  return this.request("DELETE", `/v1/sdk/devices/${enc(token)}`);
196
226
  }
197
227
  /**
198
- * Bildirime dokunuldu — açılma damgası.
228
+ * Bildirime dokunuldu - açılma damgası.
199
229
  *
200
230
  * Push'ta açılmayı YALNIZCA uygulama bilir: FCM/APNs "teslim ettim" der,
201
231
  * "kullanıcı dokundu" demez. Bildirim yükündeki `data.sb_message_id`
202
232
  * değerini buraya geri gönderin.
203
233
  *
204
234
  * ```ts
205
- * // React Native / Expo — bildirime dokunma işleyicisinde
235
+ * // React Native / Expo - bildirime dokunma işleyicisinde
206
236
  * const id = response.notification.request.content.data?.sb_message_id
207
237
  * if (id) await sb.reportPushOpened(String(id))
208
238
  * ```
@@ -219,7 +249,8 @@ var SignalbirdApp = class {
219
249
  const isForm = typeof FormData !== "undefined" && body instanceof FormData;
220
250
  const headers = {
221
251
  Accept: "application/json",
222
- "X-Signalbird-Key": this.config.publicKey
252
+ "X-Signalbird-Key": this.config.publicKey,
253
+ [SDK_HEADER]: sdkHeaderValue(appPlatform())
223
254
  };
224
255
  const moduleKey = path.startsWith("/v1/sdk/devices") || path.startsWith("/v1/sdk/push") ? this.config.pushKey : this.config.chatKey;
225
256
  if (moduleKey) headers["X-Signalbird-Module-Key"] = moduleKey;
@@ -235,6 +266,7 @@ var SignalbirdApp = class {
235
266
  body: body === void 0 ? void 0 : isForm ? body : JSON.stringify(body),
236
267
  signal: controller.signal
237
268
  });
269
+ noteSdkStatus(response.headers);
238
270
  const text = await response.text();
239
271
  let data = null;
240
272
  try {
@@ -297,6 +329,11 @@ var SignalbirdApp = class {
297
329
  }
298
330
  }
299
331
  };
332
+ function withIdentityHash(input) {
333
+ if (!input || input.identityHash === void 0) return input;
334
+ const { identityHash, ...rest } = input;
335
+ return { ...rest, identity_hash: rest.identity_hash ?? identityHash };
336
+ }
300
337
  function enc(value) {
301
338
  return encodeURIComponent(value);
302
339
  }
@@ -310,6 +347,9 @@ function buildQuery(query) {
310
347
  const encoded = params.toString();
311
348
  return encoded ? `?${encoded}` : "";
312
349
  }
350
+ function appPlatform() {
351
+ return typeof navigator !== "undefined" && navigator.product === "ReactNative" ? "react-native" : "app";
352
+ }
313
353
 
314
354
  // src/shared/socket.ts
315
355
  var BACKOFF = [1e3, 2e3, 5e3, 1e4, 3e4];
@@ -434,7 +474,7 @@ var Socket = class {
434
474
  }
435
475
  }
436
476
  /**
437
- * Olay yolla — ACK İSTEMEDEN.
477
+ * Olay yolla - ACK İSTEMEDEN.
438
478
  *
439
479
  * `subscribe`in sonucunu beklemek bir tur daha protokol yönetmek demekti;
440
480
  * oysa sonucu zaten davranıştan görüyoruz: imza tutmadıysa yayın gelmez ve
@@ -499,6 +539,8 @@ var ChatSession = class {
499
539
  * da düzenleme ekrana hiç yansımaz.
500
540
  */
501
541
  this.forceFull = false;
542
+ /** Son gönderilen `typing(true)` zamanı; 0 = açık sinyal yok. */
543
+ this.lastTypingSent = 0;
502
544
  this.active = options.active ?? false;
503
545
  }
504
546
  // ── Abonelik ──────────────────────────────────────────────────────────
@@ -533,7 +575,7 @@ var ChatSession = class {
533
575
  this.patch({ loading: false });
534
576
  this.schedule();
535
577
  }
536
- /** Panel açıldı/kapandı — yoklama hızı buna göre değişir. */
578
+ /** Panel açıldı/kapandı - yoklama hızı buna göre değişir. */
537
579
  setActive(active) {
538
580
  if (this.active === active) return;
539
581
  this.active = active;
@@ -548,7 +590,7 @@ var ChatSession = class {
548
590
  * Konuşmayı bırakır; sonraki mesaj YENİ bir konuşma açar.
549
591
  *
550
592
  * Ekranın "yeni sohbet" düğmesi de bunu çağırır. Sunucuda hiçbir şey
551
- * silinmez — yalnız bu oturumun neye baktığı değişir.
593
+ * silinmez - yalnız bu oturumun neye baktığı değişir.
552
594
  */
553
595
  reset() {
554
596
  this.patch({ conversation: null, messages: [], unread: 0, agentTyping: false, errorCode: void 0 });
@@ -562,7 +604,7 @@ var ChatSession = class {
562
604
  this.socket = null;
563
605
  this.live = false;
564
606
  }
565
- /** Canlı bağlantı kurulu mu — arayüz isterse gösterir (zorunlu değil). */
607
+ /** Canlı bağlantı kurulu mu - arayüz isterse gösterir (zorunlu değil). */
566
608
  get isLive() {
567
609
  return this.live;
568
610
  }
@@ -617,16 +659,32 @@ var ChatSession = class {
617
659
  }
618
660
  /**
619
661
  * Ziyaretçinin konu seçimi. Konuşma AÇILDIKTAN sonra çağrılırsa etkisizdir:
620
- * açılmış konuşmanın konusunu ajan panelden değiştirir — ziyaretçiye kendi
662
+ * açılmış konuşmanın konusunu ajan panelden değiştirir - ziyaretçiye kendi
621
663
  * konuşmasını yeniden sınıflandırma yetkisi vermek, atamayı da bozardı.
622
664
  */
623
665
  setTopic(slug) {
624
666
  this.patch({ topic: slug });
625
667
  }
626
- /** İlk tuşta `true`, 2.5 s hareketsizlikte `false` — çağıran zamanlar. */
668
+ /**
669
+ * İlk tuşta `true`, 2.5 s hareketsizlikte `false` - çağıran zamanlar.
670
+ *
671
+ * SDK yine de kendini korur (18 Eyl 2026): `true` 4 saniyede bir kez gider,
672
+ * `false` yalnız açık bir `true` varsa. Bir entegrasyon her tuşta
673
+ * çağırdığında (Penyu mobil öyle yapıyordu) 40 harflik mesaj 40 istek
674
+ * oluyor ve sunucunun hız sınırı ikinci mesajı 429 ile düşürüyordu. Web
675
+ * widget'ındaki kuralın aynısı.
676
+ */
627
677
  typing(isTyping) {
628
678
  const conversation = this.state.conversation;
629
679
  if (!conversation) return;
680
+ const now = Date.now();
681
+ if (isTyping) {
682
+ if (now - this.lastTypingSent < 4e3) return;
683
+ this.lastTypingSent = now;
684
+ } else {
685
+ if (this.lastTypingSent === 0) return;
686
+ this.lastTypingSent = 0;
687
+ }
630
688
  void this.app.setTyping(conversation.id, isTyping);
631
689
  }
632
690
  /** Görülen son mesaja kadar okundu işaretler. */
@@ -825,7 +883,7 @@ var SignalbirdService = class {
825
883
  }
826
884
  return this.subject.asObservable();
827
885
  }
828
- /** Panel açık mı — yoklama hızını belirler. */
886
+ /** Panel açık mı - yoklama hızını belirler. */
829
887
  setOpen(open) {
830
888
  this.session.setActive(open);
831
889
  }