signalbird 2.6.0 → 2.9.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 CHANGED
@@ -1,13 +1,13 @@
1
1
  # Signalbird SDK
2
2
 
3
- **Tek paket, dört yüzey, on iki giriş noktası.** Panelde tıklayarak yapabildiğiniz her şey
3
+ **Tek paket, beş yüzey, on iki giriş noktası.** Panelde tıklayarak yapabildiğiniz her şey
4
4
  kodla da yapılabilir.
5
5
 
6
6
  | Yüzey | Ne yapar | Anahtar | Nerede |
7
7
  |---|---|---|---|
8
- | **Telsiz** (Radio) | projenizden bir **kanala** log/olay yazar | `sb_secret_live_…` / `sb_public_live_…` | sunucu / tarayıcı |
9
- | **Gönderim** (Messaging) | e-posta, SMS, push gönderir; kişi, liste, kampanya yönetir; mesaj durumu okur; webhook imzası doğrular | `sb_…` | yalnız sunucu |
10
- | **Yönetim** (Management) | Telsiz projesi/kanalı açar, olay akışını okur, **sohbet gelen kutusunu** işler, uygulama ve cihaz yönetir | `sb_…` + scope | yalnız sunucu |
8
+ | **Telsiz** (Radio) | uygulamanızdan bir **kanala** log/olay yazar | `sb_secret_live_…` / `sb_public_live_…` | sunucu / tarayıcı |
9
+ | **Gönderim** (Messaging) | e-posta, SMS, push gönderir; kişi, liste, kampanya yönetir; mesaj durumu okur; webhook imzası doğrular | `sb_secret_live_…` | yalnız sunucu |
10
+ | **Yönetim** (Management) | kanal (modül anahtarı) açar, olay akışını okur, **sohbet gelen kutusunu** işler, push cihazlarını listeler | `sb_secret_live_…` | yalnız sunucu |
11
11
  | **Uygulama** (App) | müşterinizin **son kullanıcısına** canlı sohbet + push cihaz kaydı | `sb_public_live_…` | web, iOS, Android |
12
12
  | **Partner** | Signalbird'ü kendi ürününde satan **sözleşmeli platform** müşterisini sağlar ve yetkilendirir | `sb_secret_live_…` | yalnız sunucu |
13
13
 
@@ -32,8 +32,8 @@ Bir seçenek daha var ve kod yazmaz: hazır sohbet widget'ı
32
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
- geleneğini korur (`createRadioProject` / `create_radio_project` /
36
- `CreateRadioProject`). `node scripts/check-parity.mjs` bunu her derlemede
35
+ geleneğini korur (`createModuleKey` / `create_module_key` /
36
+ `CreateModuleKey` / `CreateModuleKeyAsync`). `node scripts/check-parity.mjs` bunu her derlemede
37
37
  denetler.
38
38
 
39
39
  Uygulama yüzeyi mobil ve tarayıcı içindir: gizli anahtar oraya gömülmez.
@@ -41,7 +41,7 @@ Gönderim ve Yönetim yüzeyleri yalnız sunucudadır.
41
41
 
42
42
  ## Telsiz
43
43
 
44
- Telsiz'in tek işi vardır: projenizden bir **kanala** mesaj yazmak.
44
+ Telsiz'in tek işi vardır: uygulamanızdan bir **kanala** mesaj yazmak.
45
45
 
46
46
  Bildirimin kime gideceği, hangi kanaldan (push/e-posta), sessiz saatlerde ne
47
47
  olacağı ve aynı mesajın kaç kez uyarı üreteceği **sunucuda, kanal ayarlarında**
@@ -49,11 +49,14 @@ durur. Kod tarafında bunlar yoktur ve olmamalıdır: bildirim kuralını
49
49
  değiştirmek için uygulamanızı yeniden yayınlamanız gerekmesin.
50
50
 
51
51
  ```
52
- proje → penyu.io (anahtarın sahibi)
53
- kanal → critical, info, deploy… (bildirim kuralı burada)
54
- olay → tek bir kayıt
52
+ domain anahtarı → sb_secret_live_… / sb_public_live_… (kimlik: alan adınızın anahtarı)
53
+ modül anahtarı → odemeHatasi, deploy, browser… (kanal adı; bildirim kuralı burada)
54
+ olay → tek bir kayıt
55
55
  ```
56
56
 
57
+ Kanal adı (modül anahtarı) **gizli değildir** ve kodun içinde durur: domain
58
+ anahtarı olmadan hiçbir işe yaramaz. Gövdede `key` alanıyla gider.
59
+
57
60
  ## Tek anahtar
58
61
 
59
62
  `.env` dosyanıza **bir satır** yazarsınız:
@@ -62,13 +65,13 @@ olay → tek bir kayıt
62
65
  SIGNALBIRD_DOMAIN_KEY=sb_secret_live_…
63
66
  ```
64
67
 
65
- Takım anahtarı (`sb_…`) her şeyi kapsar: e-posta, SMS, push, kişi ve kampanya,
66
- sohbet, uygulama, domain, Telsiz log yazımı. Ne yapabileceğini anahtarın
67
- kapsamları belirler; panelde üretilirken seçersiniz. **Adres yazmanız
68
- gerekmez** - üretim kökü paketin içindedir.
69
-
70
- Ayrı anahtar isteğe bağlıdır: bir sunucunun yalnız log yazıp gönderim
71
- yapamamasını istiyorsanız o sunucuya dar kapsamlı ikinci bir anahtar verirsiniz.
68
+ Bu, alan adınızın **gizli domain anahtarıdır** (Panel → Alan adları →
69
+ Anahtarlar) ve sunucu yüzeylerinin hepsini kapsar: Telsiz log yazımı, e-posta,
70
+ SMS, push, kişi ve kampanya, Yönetim. Kapsam (scope) listesi yoktur (v2.0);
71
+ ayrım anahtarın **türündedir** - gizli (`sb_secret_live_…`, sunucu) ya da açık
72
+ (`sb_public_live_…`, istemci). Hangi kanala yazdığınızı **modül anahtarı**
73
+ (kanal adı) belirler. **Adres yazmanız gerekmez** - üretim kökü paketin
74
+ içindedir.
72
75
 
73
76
  Bir de **açık** anahtarlar vardır; onlar `.env`'e değil, sayfanın içine gömülür
74
77
  ve gizli olmadıkları için ayrı durmak zorundadırlar:
@@ -76,7 +79,7 @@ ve gizli olmadıkları için ayrı durmak zorundadırlar:
76
79
  | Anahtar | Nerede | Ne yapar |
77
80
  |---|---|---|
78
81
  | `sb_public_live_…` | `<script data-key data-channel>` | sohbet widget'ı, push cihaz kaydı |
79
- | `sb_public_live_…` | tarayıcı log istemcisi | yalnız izinli alan adlarından |
82
+ | `sb_public_live_…` | tarayıcı log istemcisi | yalnız izinli kökenlerden (alan adları) |
80
83
 
81
84
  Gizli anahtar tarayıcıya **gömülemez**: sunucu, `Origin` başlığı taşıyan bir
82
85
  istekte gizli anahtarı reddeder (`SECRET_KEY_IN_BROWSER`). Bu bir kolaylık
@@ -94,7 +97,7 @@ değil, kasıtlı bir duvardır - anahtar bir kez istemciye indiğinde herkesind
94
97
  | Go | `go get github.com/Pariette-Inc/signalbird.sdk` |
95
98
  | .NET, ASP.NET Core | `dotnet add package Signalbird.Sdk` |
96
99
  | Swift (iOS, macOS) | SPM: `https://github.com/Pariette-Inc/signalbird.sdk` |
97
- | Kotlin (Android) | `implementation("io.signalbird:signalbird-sdk:2.6.0")` |
100
+ | Kotlin (Android) | `implementation("io.signalbird:signalbird-sdk:2.9.0")` |
98
101
  | 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
102
 
100
103
  > Hepsi **bu repodan** çıkar ve **aynı sürümü** taşır - ayrı SDK reposu ya da
@@ -125,7 +128,10 @@ const sb = new SignalbirdClient({
125
128
  throwOnError: false, // üretimde kapalı kalmalı
126
129
  })
127
130
 
128
- await sb.log({ channel: 'deploy', message: 'v2.4.0 yayında', level: 'info' })
131
+ await sb.log({ key: 'deploy', message: 'v2.4.0 yayında', level: 'info' })
132
+
133
+ // Kanalı bir kez bağlayıp yazmak:
134
+ await sb.radio('deploy').info('v2.4.0 yayında')
129
135
  ```
130
136
 
131
137
  **Yakalanmamış hatalar:**
@@ -138,8 +144,8 @@ signalbird().captureUncaught('critical')
138
144
 
139
145
  ```ts
140
146
  const result = await signalbird().batch([
141
- { channel: 'info', message: 'iş 1 bitti' },
142
- { channel: 'info', message: 'iş 2 bitti' },
147
+ { key: 'isler', message: 'iş 1 bitti', level: 'info' },
148
+ { key: 'isler', message: 'iş 2 bitti', level: 'info' },
143
149
  ])
144
150
  ```
145
151
 
@@ -178,10 +184,22 @@ const sb = initSignalbird({
178
184
 
179
185
  sb.captureErrors('browser') // window.onerror + unhandledrejection
180
186
  sb.error('browser', 'sepet güncellenemedi', { cartId })
187
+
188
+ // Kanalı bir kez bağlamak (2.9.0): debug · info · warn · error
189
+ const sepet = sb.radio('sepet')
190
+ sepet.warn('stok azaldı', { sku })
181
191
  ```
182
192
 
193
+ Tarayıcı istemcisinin metotları: `log(key, message, level?, context?)`,
194
+ `info`, `warn`, `error`, `radio(key)`, `captureErrors(key?)`, `flush()`,
195
+ `destroy()`. Ayrı bir `critical()` / `debug()` kısayolu **yoktur**: istemci
196
+ kodu herkesin elindedir ve oradan kritik alarm çaldırmak kötüye kullanıma
197
+ açıktır (gerekirse `log(key, msg, 'critical')`; kanalın bildirimi panelde
198
+ susturulabilir).
199
+
183
200
  Kayıtlar tek tek değil, **toplu** gider (varsayılan 3 saniyede bir) ve sekme
184
- kapanırken `sendBeacon` ile boşaltılır.
201
+ kapanırken `sendBeacon` ile boşaltılır (`text/plain` gövde - tarayıcı
202
+ cross-origin beacon'da JSON içerik türüne izin vermez).
185
203
 
186
204
  **React** - `app/providers.tsx` ya da `main.tsx`:
187
205
 
@@ -209,9 +227,12 @@ export class SignalbirdErrorHandler implements ErrorHandler {
209
227
  }
210
228
  ```
211
229
 
212
- Panelde bu projenin **izinli kökenlerini** ve **izinli kanallarını** açmayı
213
- unutmayın; ikisi de boşken tarayıcı anahtarı hiçbir şey yapamaz. Kritik
214
- kanalları tarayıcıya açmayın: istemci kodu herkesin elindedir.
230
+ Panelde açık anahtarın **izinli kökenlerini** (alan adlarını) tanımlamayı
231
+ unutmayın; liste boşken tarayıcı anahtarı hiçbir şey yazamaz
232
+ (`ORIGIN_NOT_ALLOWED`). Kanal bazında bir kısıt **yoktur**: açık anahtar,
233
+ izinli kökenden her kanala yazabilir. Bu yüzden tarayıcıdan yazılan kanalları
234
+ (`browser` gibi) sunucunun kritik kanallarından ayrı tutun ve bildirim
235
+ kurallarını ona göre verin - istemci kodu herkesin elindedir.
215
236
 
216
237
  ## PHP / Laravel
217
238
 
@@ -238,13 +259,15 @@ SIGNALBIRD_SOURCE=api-01
238
259
  'signalbird' => [
239
260
  'driver' => 'monolog',
240
261
  'handler' => \Signalbird\Sdk\SignalbirdLogHandler::class,
241
- 'with' => ['channel' => 'laravel'],
262
+ 'with' => ['key' => 'laravel'], // kanal adı (modül anahtarı)
242
263
  'level' => 'error',
243
264
  ],
244
265
  ```
245
266
 
246
267
  Sonra `LOG_STACK=single,signalbird`. Mevcut `Log::error()` satırlarınız olduğu
247
- gibi çalışır; tek satır kod yazmadan Telsiz'e düşerler.
268
+ gibi çalışır; tek satır kod yazmadan Telsiz'e düşerler. `context` içindeki
269
+ istisna (`['exception' => $e]`) sınıf, mesaj, dosya, satır ve ilk 20 yığın
270
+ çerçevesiyle gider (2.9.0).
248
271
 
249
272
  Laravel dışı PHP:
250
273
 
@@ -255,6 +278,36 @@ Signalbird::configure('sb_secret_live_…');
255
278
  Signalbird::error('api', 'veritabanı bağlantısı koptu');
256
279
  ```
257
280
 
281
+ ## AWS / webhook ile Telsiz'e yazmak
282
+
283
+ SDK kurmanın mümkün olmadığı ya da gereksiz olduğu kaynaklar (AWS alarmları,
284
+ CI, üçüncü parti servisler) Telsiz kanalına **gelen bağlantı adresiyle**
285
+ yazar. Her kanalın kendi adresi vardır; panelde **Telsiz → Kanallar → AWS /
286
+ Webhook** altında oluşturulur:
287
+
288
+ ```
289
+ POST https://live.signalbird.io/api/v1/radio/hook/sbh_…
290
+ ```
291
+
292
+ - Kimlik URL'deki kanal jetonudur (`sbh_…`), domain anahtarı değil: jetonun
293
+ yetkisi yalnız o kanala log yazmaktır. Jetonu bir sır gibi saklayın;
294
+ sızarsa panelden yenileyin.
295
+ - **AWS SNS:** konuya HTTPS aboneliği olarak bu adresi ekleyin. Abonelik
296
+ onayı (`SubscriptionConfirmation`) kendiliğinden yapılır ve her SNS
297
+ mesajının imzası doğrulanır.
298
+ - **CloudWatch alarmları:** `ALARM` → `critical`, `OK` → `info` seviyesinde
299
+ yazılır.
300
+ - **Herhangi bir sistem** JSON gönderebilir:
301
+
302
+ ```bash
303
+ curl -X POST https://live.signalbird.io/api/v1/radio/hook/sbh_… \
304
+ -H 'Content-Type: application/json' \
305
+ -d '{"message": "yedekleme başarısız", "level": "error"}'
306
+ ```
307
+
308
+ `level` verilmezse kanalın varsayılan seviyesi geçerlidir; isteğe bağlı
309
+ `context` (nesne) ve `source` alanları da kabul edilir. SDK gerekmez.
310
+
258
311
  ## Davranış kuralları
259
312
 
260
313
  - **Sessiz hata varsayılandır.** Telsiz erişilemezse çağrı `ok: false` döner ve
@@ -268,16 +321,26 @@ Signalbird::error('api', 'veritabanı bağlantısı koptu');
268
321
  - **Kritik seviye sessiz saatleri deler.** Gece üçte ölen servis sabahı bekleyemez.
269
322
  - **Tekrar bastırma kaydı değil bildirimi susturur.** Aynı mesaj kanalın
270
323
  `dedupe` süresi içinde tekrar gelirse ikinci bildirim gitmez ama kayıt tutulur.
324
+ - **Mesaj 4000 karaktere kırpılır (2.9.0).** API daha uzununu 422 ile reddeder
325
+ ve toplu gönderimde tek uzun satır bütün paketi düşürürdü; SDK göndermeden
326
+ önce kırpar. `context` güvenle serileştirilir: döngüsel başvuru
327
+ `"[Circular]"`, `Error` `{name, message, stack}`, `BigInt` metin olur (PHP'de
328
+ `Throwable` → `class, message, code, file, line, trace`).
271
329
  - **SDK sürümünü bildirir (2.6.0).** Her istek `X-Signalbird-Sdk:
272
330
  <platform>/<sürüm>` taşır. Yeni sürüm çıktığında SDK süreç başına **bir kez**
273
331
  uyarı yazar; eski sürümdeki anahtarların sahiplerine panel bildirimi gider.
274
332
  Uyarı hata değildir, istek sonucu değişmez (CONTRACT §14).
333
+ - **Ziyaretçi kimliği doğrulanır (2.7.0).** Sohbet/push ziyaretçisinin
334
+ `external_id`'si ancak sunucunuzda üretilen `identity_hash` ile birlikte
335
+ gelirse güvenilir sayılır: kişi kaydına bağlama, cihaza push ve kanal
336
+ ajanının araçlarına giden güvenilir kimlik buna bağlıdır. Widget ayrıca
337
+ gerektiğinde Cloudflare Turnstile ister (CONTRACT §15).
275
338
 
276
339
  ## Gönderim (Messaging)
277
340
 
278
- Takım API anahtarı (`sb_…`, panelde **Konsol → API anahtarları**, scope'lu)
279
- ile çalışır. Telsiz anahtarı burada geçmez - istemci kurulurken
280
- `WRONG_KEY_TYPE` ile reddeder. Yalnız sunucuda kullanılır.
341
+ Telsiz ile **aynı** gizli domain anahtarıyla (`sb_secret_live_…`) çalışır.
342
+ Açık anahtar burada geçmez - istemci kurulurken `WRONG_KEY_TYPE` ile reddeder.
343
+ Yalnız sunucuda kullanılır.
281
344
 
282
345
  Node:
283
346
 
@@ -294,6 +357,16 @@ const r = await sb.sendEmail({
294
357
  })
295
358
  if (!r.ok) console.error(r.code, r.message) // ok:false → code + message
296
359
 
360
+ // Panelde tanımlı şablonla ve gönderici kanalıyla (PHP: Signalbird::sendMail('noReply')):
361
+ await sb.sendEmail({
362
+ to: 'ali@example.com',
363
+ class: 'transactional',
364
+ template: 'Sipariş Onayı', // ad ya da template_id; varken subject/body gerekmez
365
+ vars: { ad: 'Ali', no: 'S-1234' },
366
+ module_key: 'noReply', // From adresini kanal seçer
367
+ reply_to: 'destek@ornek.com',
368
+ })
369
+
297
370
  await sb.sendSms({ to: '+905551112233', class: 'transactional', body: 'Kodunuz: 4821' })
298
371
  await sb.sendPush({ to: 'external:user-1042', class: 'transactional', subject: 'Yeni mesaj', body: '…' })
299
372
 
@@ -327,7 +400,8 @@ if (! $r['ok']) { Log::warning($r['code'], $r); }
327
400
 
328
401
  `.env`: `SIGNALBIRD_DOMAIN_KEY=sb_secret_live_…` (isteğe bağlı `SIGNALBIRD_MESSAGING_URL`,
329
402
  `SIGNALBIRD_MESSAGING_TIMEOUT`). Laravel dışı PHP:
330
- `Signalbird::configureMessaging('sb_…')` ya da `new MessagingClient('sb_…')`.
403
+ `Signalbird::configureMessaging('sb_secret_live_…')` ya da
404
+ `new MessagingClient('sb_secret_live_…')`.
331
405
 
332
406
  Metot kümesi iki dilde aynıdır: `sendEmail` `sendSms` `previewSms` `sendPush` ·
333
407
  `listContacts` `createContact` `updateContact` `deleteContact` `bulkContacts` ·
@@ -366,27 +440,30 @@ değil.
366
440
  takımın kayıtlarına dokunur. Kullanıcı, faturalama ve abonelik işlemleri SDK'da
367
441
  yoktur.
368
442
 
369
- Panelden `radio:*`, `chat:*`, `apps:*` scope'larıyla bir `sb_…` anahtarı açın.
443
+ Aynı gizli domain anahtarıyla (`sb_secret_live_…`) çalışır; ayrı anahtar ya
444
+ da kapsam (scope) gerekmez. Domain anahtarının kendisi panelden üretilir ve
445
+ döndürülür - SDK anahtar üretmez.
370
446
 
371
447
  ```ts
372
448
  import { management } from 'signalbird'
373
449
 
374
- // Yeni ortam kurulumu: proje aç, kanalını tanımla, anahtarı sakla
375
- const { data } = await management().createRadioProject({ name: 'ödeme-servisi' })
376
-
377
- // `secret` YALNIZ burada döner - sunucuda yalnız özeti saklanır
378
- await vault.write('SIGNALBIRD_DOMAIN_KEY', data!.secret)
379
-
380
- await management().createRadioChannel(data!.project.id, {
381
- key: 'odeme',
382
- name: 'Ödeme',
450
+ // Yeni ortam kurulumu: Telsiz kanalını (modül anahtarını) bildirim kuralıyla aç
451
+ const { data } = await management().createModuleKey('logger', {
452
+ key: 'odeme', // kodda kullanacağınız ad: signalbird().radio('odeme')
453
+ title: 'Ödeme',
383
454
  level: 'critical',
384
- notify_push: true,
455
+ notify: ['push'],
385
456
  quiet_from: 0,
386
- quiet_to: 7, // kritik seviye sessiz saatleri yine de deler
457
+ quiet_to: 7, // kritik seviye sessiz saatleri yine de deler
387
458
  })
459
+ console.log(data?.module_key.id)
388
460
  ```
389
461
 
462
+ `module` ∈ `logger` (Telsiz) · `email` · `sms` · `push` · `chat`. Diğer
463
+ metotlar: `listModuleKeys`, `getModuleKey`, `updateModuleKey` (ad
464
+ değiştirilebilir; eski ad 30 gün kabul edilir), `deleteModuleKey`,
465
+ `listModuleKeyDevices`, `radioSummary`, `radioEvents`.
466
+
390
467
  Sohbet gelen kutusunu kendi botunuzla işleyin:
391
468
 
392
469
  ```ts
@@ -409,25 +486,26 @@ const { data } = await management().embedToken({ module: 'chat' })
409
486
  // data.url → 120 saniyelik, TEK KULLANIMLIK adres; doğrudan <iframe>'e verin
410
487
  ```
411
488
 
412
- Anahtar `embed:issue` kapsamı ister - jeton 60 dakikalık bir panel oturumuna
413
- çevrildiği için bu kapsam bilerek ayrıdır.
489
+ Anahtarın panelde **gömme jetonu üretebilir** (`can_issue_embed`) onayı
490
+ taşıması gerekir; yoksa 403 `EMBED_NOT_ALLOWED`. Jeton 60 dakikalık bir panel
491
+ oturumuna çevrildiği için bu onay bilerek ayrıdır.
414
492
 
415
493
  Aynısı PHP, Python, Go ve .NET'te birebir aynı metot adlarıyla:
416
494
 
417
495
  ```php
418
- Signalbird::management()->createRadioProject(['name' => 'ödeme-servisi']);
496
+ Signalbird::management()->createModuleKey('logger', ['key' => 'odeme', 'title' => 'Ödeme']);
419
497
  ```
420
498
 
421
499
  ```python
422
- signalbird.SignalbirdManagement(api_key=key).create_radio_project({"name": "ödeme-servisi"})
500
+ signalbird.SignalbirdManagement(domain_key=key).create_module_key("logger", {"key": "odeme", "title": "Ödeme"})
423
501
  ```
424
502
 
425
503
  ```go
426
- admin.CreateRadioProject(ctx, map[string]any{"name": "ödeme-servisi"})
504
+ admin.CreateModuleKey(ctx, "logger", map[string]any{"key": "odeme", "title": "Ödeme"})
427
505
  ```
428
506
 
429
507
  ```csharp
430
- await management.CreateRadioProjectAsync(new { name = "ödeme-servisi" });
508
+ await management.CreateModuleKeyAsync("logger", new Dictionary<string, object?> { ["key"] = "odeme", ["title"] = "Ödeme" });
431
509
  ```
432
510
 
433
511
  Tam liste (40 metot): `docs/CONTRACT.md § 10`.
@@ -489,8 +567,8 @@ zaten saklanmıyor.
489
567
  ## Gönderim - müşterinin kendi sisteminden
490
568
 
491
569
  Bir platformun (SubmitCMS, veribenim…) müşterisiyseniz kendi sunucunuzdan da
492
- gönderim yapabilirsiniz. Gereken tek şey **kendi takım anahtarınızdır**
493
- (`sb_…`); panelinizdeki entegrasyon kartında durur.
570
+ gönderim yapabilirsiniz. Gereken tek şey **kendi gizli domain anahtarınızdır**
571
+ (`sb_secret_live_…`); panelde Alan adları → Anahtarlar altında durur.
494
572
 
495
573
  ```ts
496
574
  import { SignalbirdMessaging } from 'signalbird'
@@ -642,11 +720,43 @@ Signalbird.chat.open() // close() · toggle() · isOpen()
642
720
  Signalbird.chat.on('unread', (n) => badge.textContent = n)
643
721
  Signalbird.push.register({ token, platform: 'web', provider: 'fcm' })
644
722
  Signalbird.destroy()
723
+ Signalbird.reset() // kullanıcı ÇIKIŞ yapınca (2.8.0, CONTRACT §15.3)
645
724
  ```
646
725
 
726
+ **Çıkışta `Signalbird.reset()` zorunlu (2.8.0).** Ziyaretçi sırrını, sohbet
727
+ geçmişini ve kimliği tarayıcıdan siler; aynı tarayıcıyı kullanan sonraki kişi
728
+ öncekinin sohbetini görmez, destek ajanı onu önceki kullanıcı sanmaz.
729
+ Sunucu da kimliği her oturumda yeniden imza ister.
730
+
647
731
  `data-key`/`data-channel` yerine `Signalbird.init({ publicKey, chatKey, baseUrl?, locale? })` da
648
- çağrılabilir. Widget ev sahibi sayfaya asla hata fırlatmaz; Shadow DOM içinde
649
- çalışır, sayfanızın CSS'iyle çakışmaz; < 20 KB gzip. Ayrıntı:
732
+ çağrılabilir.
733
+
734
+ **Kimlik doğrulaması (2.7.0).** Oturum açmış kullanıcıyı tanıtırken
735
+ `external_id`'nin hash'ini SUNUCUNUZDA üretip birlikte verin; yoksa Signalbird
736
+ bu kimliği doğrulanmamış sayar (kişi kaydına bağlamaz, cihaza push hedeflemez,
737
+ ajan araçları kimliği yalnız `visitor.unverified` altında görür):
738
+
739
+ ```php
740
+ // Blade - gizli anahtar sunucuda kalır
741
+ <script async src="https://signalbird.io/sdk/v1/signalbird.js"
742
+ data-key="sb_public_live_…" data-channel="destek"
743
+ data-external-id="{{ $user->id }}"
744
+ data-identity-hash="{{ Signalbird::identityHash((string) $user->id) }}"></script>
745
+ ```
746
+
747
+ ```js
748
+ Signalbird.identify({ external_id: 'user-1042', identityHash: '<sunucudan>', email: 'ali@example.com' })
749
+ ```
750
+
751
+ `identity_hash = hex(HMAC-SHA256(hex(SHA-256(sb_secret_live_…)), external_id))`;
752
+ Node `client.identityHash(id)`, Python `client.identity_hash(id)`, Go
753
+ `client.IdentityHash(id)`, .NET `client.IdentityHash(id)`.
754
+
755
+ **Captcha.** Kanalda açıksa widget yeni ziyaretçide ve doğrulanmamış
756
+ ziyaretçinin ilk konuşmasında görünmez bir Cloudflare Turnstile jetonu alır;
757
+ betik yalnız panel ilk açıldığında yüklenir, etkileşim yalnız Cloudflare
758
+ isterse görünür. Kurulumda yapmanız gereken bir şey yoktur. Widget ev sahibi sayfaya asla hata fırlatmaz; Shadow DOM içinde
759
+ çalışır, sayfanızın CSS'iyle çakışmaz; < 40 KB gzip. Ayrıntı:
650
760
  `docs/CONTRACT.md § 9` ve https://signalbird.io/sdk/widget.
651
761
 
652
762
  ## Gömme (embed) - Signalbird ekranını kendi panelinizde çalıştırın
@@ -689,21 +799,42 @@ Sunucu tarafı tek çağrıdır (`Signalbird::partner()->createEmbedToken(...)`,
689
799
 
690
800
  ## Hata kodları
691
801
 
692
- | Kod | Anlamı |
693
- |---|---|
694
- | `INVALID_KEY` | Anahtar yok, yanlış ya da proje pasif |
695
- | `SECRET_KEY_IN_BROWSER` | Gizli anahtar tarayıcıdan kullanıldı |
696
- | `ORIGIN_NOT_ALLOWED` | Tarayıcı anahtarı bu alan adına açık değil |
697
- | `CHANNEL_NOT_ALLOWED` | Tarayıcı anahtarı bu kanala yazamaz |
698
- | `MODULE_DISABLED` | Paketinizde Telsiz (`logger`) modülü yok |
699
- | `LIMIT_REACHED` | Aylık kayıt limitiniz doldu |
700
- | `CHANNEL_DISABLED` | Kanal kapalı - kayıt yazılmaz, kota da harcanmaz |
701
-
702
- Gönderim ve Yönetim istemcilerine özgü: `WRONG_KEY_TYPE` (kurulumda),
703
- `API_KEY_INVALID`, `API_KEY_SCOPE` (anahtarda gereken scope yok),
704
- `VALIDATION_ERROR` (422), `NO_CONSENT`, `SUPPRESSED`, `NO_SENDING_DOMAIN`,
705
- `LIST_NOT_FOUND`, `MODULE_DISABLED`, `NETWORK_ERROR`, `TIMEOUT`, `HTTP_<durum>`.
802
+ Telsiz (`/v1/radio/log`, `/v1/radio/log/batch`) ve bütün anahtarlı uçların
803
+ ortak kapısı:
804
+
805
+ | Kod | HTTP | Anlamı |
806
+ |---|---|---|
807
+ | `DOMAIN_KEY_MISSING` | 401 | `X-Signalbird-Key` başlığı yok |
808
+ | `DOMAIN_KEY_INVALID` | 401 | Anahtar tanınmadı ya da kullanım dışı (iptal edilmiş) |
809
+ | `SECRET_KEY_IN_BROWSER` | 401 | Gizli anahtar `Origin` taşıyan (tarayıcı) bir istekte kullanıldı |
810
+ | `SECRET_KEY_IN_QUERY` | 401 | Gizli anahtar sorgu dizesinde (`?k=`) gönderildi |
811
+ | `SECRET_KEY_REQUIRED` | 403 | Uç gizli anahtar ister; açık anahtar verildi |
812
+ | `ORIGIN_REQUIRED` | 403 | Açık (web) anahtar `Origin`'siz, yani sunucudan kullanıldı |
813
+ | `ORIGIN_NOT_ALLOWED` | 403 | İstek kökeni açık anahtarın izinli kökenleri arasında değil |
814
+ | `APP_KEY_IN_BROWSER` | 403 | Mobil uygulama anahtarı web sayfasından kullanıldı |
815
+ | `DOMAIN_INACTIVE` / `TEAM_INACTIVE` | 403 | Alan adı ya da takım pasif |
816
+ | `MODULE_DISABLED` | 403 | Paketinizde Telsiz (`logger`) modülü yok |
817
+ | `MODULE_KEY_INVALID` | 422 | Kanal adı (`key`) geçersiz - normalize edilince boş kalıyor |
818
+ | `MODULE_KEY_DISABLED` | **202** | Kanal panelde kapalı - kayıt yazılmaz, kota da harcanmaz; yanıt `ok: false` taşır, tekrar denemeyin |
819
+ | `LIMIT_REACHED` | 429 | Aylık kayıt limitiniz doldu |
820
+ | `OVERAGE_CEILING_REACHED` | 429 | Aşım tavanına ulaşıldı |
821
+ | `NETWORK_ERROR` | - | SDK sunucuya ulaşamadı (istemci tarafı) |
822
+
823
+ Toplu uçta (`/log/batch`) kapı hatası tüm isteği reddeder; `MODULE_*` ve
824
+ kota kodları ise satır satır `results[i].code` içinde döner. Gövde
825
+ doğrulaması (ör. 4000 karakteri aşan `message`) satır satır DEĞİLDİR - tek
826
+ geçersiz satır bütün paketi 422 ile düşürür; SDK bu yüzden mesajı göndermeden
827
+ kırpar (2.9.0).
828
+
829
+ Gönderim ve Yönetim istemcilerine özgü: `WRONG_KEY_TYPE` (kurulumda, SDK),
830
+ `NO_CONSENT`, `SUPPRESSED`, `NO_SENDING_DOMAIN`, `SENDER_NOT_CONFIGURED`
831
+ (gönderici kanalına adres bağlanmamış), `MODULE_KEY_NOT_FOUND`,
832
+ `LIST_NOT_FOUND`, `EMBED_NOT_ALLOWED`, `MODULE_DISABLED`. SDK eşlemeleri
833
+ (sunucu kod döndürmediğinde): 401 → `API_KEY_INVALID`, 422 →
834
+ `VALIDATION_ERROR`, diğerleri `HTTP_<durum>`; ağ tarafı `NETWORK_ERROR`,
835
+ `TIMEOUT`.
706
836
 
707
837
  Uygulama yüzeyi ve widget: `VISITOR_INVALID` (yerel kimlik silinir, yeni
708
838
  oturum açılır), `CHAT_UNAVAILABLE` (kota - "sohbet kullanılamıyor" bandı),
709
- `NOT_INITIALIZED`.
839
+ `NOT_INITIALIZED`, `CAPTCHA_REQUIRED` / `CAPTCHA_INVALID` (403 - widget yeni
840
+ jetonla bir kez yeniden dener), `CONVERSATION_RATE_LIMITED` (429 - nazik bant).
package/dist/angular.js CHANGED
@@ -5,7 +5,7 @@ var rxjs = require('rxjs');
5
5
  // src/angular/index.ts
6
6
 
7
7
  // src/shared/version.ts
8
- var SDK_VERSION = "2.6.0" ;
8
+ var SDK_VERSION = "2.9.0" ;
9
9
  var SDK_HEADER = "X-Signalbird-Sdk";
10
10
  function sdkHeaderValue(platform) {
11
11
  return `${platform}/${SDK_VERSION}`;
@@ -64,7 +64,7 @@ var SignalbirdApp = class {
64
64
  }
65
65
  if (!config.publicKey.startsWith("sb_public_live_")) {
66
66
  throw new Error(
67
- "Signalbird: uygulama istemcisi a\xE7\u0131k domain anahtar\u0131 ister (sb_public_live_\u2026). Tak\u0131m anahtar\u0131n\u0131 (sb_\u2026) istemci koduna KOYMAYIN."
67
+ "Signalbird: uygulama istemcisi a\xE7\u0131k domain anahtar\u0131 ister (sb_public_live_\u2026). Gizli anahtar\u0131 (sb_secret_live_\u2026) istemci koduna KOYMAYIN."
68
68
  );
69
69
  }
70
70
  this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
@@ -73,9 +73,19 @@ var SignalbirdApp = class {
73
73
  this.doFetch = config.fetchImpl ?? ((...args) => fetch(...args));
74
74
  }
75
75
  // ── Kimlik ────────────────────────────────────────────────────────────
76
- /** Uygulama ayarları: sohbet açık mı, renk, çalışma saati, ön-form. */
77
- bootstrap() {
78
- 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
+ });
79
89
  }
80
90
  /**
81
91
  * Canlı bağlantı kanalı için imza.
@@ -94,7 +104,7 @@ var SignalbirdApp = class {
94
104
  * derse yerel kimlik silinir ve bir sonraki çağrı yeni oturum açar.
95
105
  */
96
106
  async startSession(input = {}) {
97
- const result = await this.request("POST", "/v1/sdk/chat/session", input);
107
+ const result = await this.request("POST", "/v1/sdk/chat/session", withIdentityHash(input));
98
108
  const visitor = result.data?.visitor;
99
109
  if (result.ok && visitor?.id && visitor.secret) {
100
110
  await this.storeVisitor({
@@ -109,7 +119,7 @@ var SignalbirdApp = class {
109
119
  }
110
120
  /** Oturum açmış kullanıcıyı ziyaretçiye bağlar (kişi kaydı upsert edilir). */
111
121
  identify(input) {
112
- return this.request("POST", "/v1/sdk/identify", input);
122
+ return this.request("POST", "/v1/sdk/identify", withIdentityHash(input));
113
123
  }
114
124
  /** Saklanan ziyaretçi kimliği - yoksa `null`. */
115
125
  async currentVisitor() {
@@ -208,7 +218,7 @@ var SignalbirdApp = class {
208
218
  * göstereceği ürün kararıdır, kütüphane kararı değil.
209
219
  */
210
220
  registerDevice(input) {
211
- return this.request("POST", "/v1/sdk/devices", input);
221
+ return this.request("POST", "/v1/sdk/devices", withIdentityHash(input));
212
222
  }
213
223
  /** Çıkışta çağrılır: kayıt silinmez, kapatılır (geçmiş korunur). */
214
224
  unregisterDevice(token) {
@@ -319,6 +329,11 @@ var SignalbirdApp = class {
319
329
  }
320
330
  }
321
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
+ }
322
337
  function enc(value) {
323
338
  return encodeURIComponent(value);
324
339
  }