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.
- package/README.md +576 -0
- package/dist/angular.d.mts +53 -0
- package/dist/angular.d.ts +53 -0
- package/dist/angular.js +578 -0
- package/dist/angular.js.map +1 -0
- package/dist/angular.mjs +574 -0
- package/dist/angular.mjs.map +1 -0
- package/dist/app.d.mts +343 -0
- package/dist/app.d.ts +343 -0
- package/dist/app.js +518 -0
- package/dist/app.js.map +1 -0
- package/dist/app.mjs +514 -0
- package/dist/app.mjs.map +1 -0
- package/dist/browser.d.mts +56 -0
- package/dist/browser.d.ts +56 -0
- package/dist/browser.js +113 -0
- package/dist/browser.js.map +1 -0
- package/dist/browser.mjs +109 -0
- package/dist/browser.mjs.map +1 -0
- package/dist/index.d.mts +1170 -0
- package/dist/index.d.ts +1170 -0
- package/dist/index.js +853 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +841 -0
- package/dist/index.mjs.map +1 -0
- package/dist/react-native.d.mts +46 -0
- package/dist/react-native.d.ts +46 -0
- package/dist/react-native.js +576 -0
- package/dist/react-native.js.map +1 -0
- package/dist/react-native.mjs +569 -0
- package/dist/react-native.mjs.map +1 -0
- package/dist/react.d.mts +43 -0
- package/dist/react.d.ts +43 -0
- package/dist/react.js +587 -0
- package/dist/react.js.map +1 -0
- package/dist/react.mjs +580 -0
- package/dist/react.mjs.map +1 -0
- package/dist/signalbird.js +141 -0
- package/dist/vue.d.mts +46 -0
- package/dist/vue.d.ts +46 -0
- package/dist/vue.js +575 -0
- package/dist/vue.js.map +1 -0
- package/dist/vue.mjs +568 -0
- package/dist/vue.mjs.map +1 -0
- package/package.json +125 -0
package/dist/app.js
ADDED
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/app/client.ts
|
|
4
|
+
var DEFAULT_BASE_URL = "https://signalbird.io/api";
|
|
5
|
+
var STORAGE_KEY = "sb_visitor";
|
|
6
|
+
function defaultStorage() {
|
|
7
|
+
try {
|
|
8
|
+
if (typeof localStorage !== "undefined") {
|
|
9
|
+
return {
|
|
10
|
+
getItem: (k) => localStorage.getItem(k),
|
|
11
|
+
setItem: (k, v) => localStorage.setItem(k, v),
|
|
12
|
+
removeItem: (k) => localStorage.removeItem(k)
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
} catch {
|
|
16
|
+
}
|
|
17
|
+
const memory = /* @__PURE__ */ new Map();
|
|
18
|
+
return {
|
|
19
|
+
getItem: (k) => memory.get(k) ?? null,
|
|
20
|
+
setItem: (k, v) => void memory.set(k, v),
|
|
21
|
+
removeItem: (k) => void memory.delete(k)
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function clientId() {
|
|
25
|
+
try {
|
|
26
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
27
|
+
return crypto.randomUUID();
|
|
28
|
+
}
|
|
29
|
+
} catch {
|
|
30
|
+
}
|
|
31
|
+
return `c_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
32
|
+
}
|
|
33
|
+
var SignalbirdApp = class {
|
|
34
|
+
constructor(config) {
|
|
35
|
+
this.config = config;
|
|
36
|
+
this.visitor = null;
|
|
37
|
+
this.loaded = false;
|
|
38
|
+
if (!config?.appKey) {
|
|
39
|
+
throw new Error("Signalbird: appKey zorunlu (sbw_pub_\u2026).");
|
|
40
|
+
}
|
|
41
|
+
if (!config.appKey.startsWith("sbw_pub_")) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
"Signalbird: uygulama istemcisi a\xE7\u0131k uygulama anahtar\u0131 ister (sbw_pub_\u2026). Tak\u0131m anahtar\u0131n\u0131 (sb_\u2026) istemci koduna KOYMAYIN."
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
47
|
+
this.storage = config.storage ?? defaultStorage();
|
|
48
|
+
this.timeout = config.timeout ?? 1e4;
|
|
49
|
+
this.doFetch = config.fetchImpl ?? ((...args) => fetch(...args));
|
|
50
|
+
}
|
|
51
|
+
// ── Kimlik ────────────────────────────────────────────────────────────
|
|
52
|
+
/** Uygulama ayarları: sohbet açık mı, renk, çalışma saati, ön-form. */
|
|
53
|
+
bootstrap() {
|
|
54
|
+
return this.request("POST", "/v1/sdk/bootstrap", { locale: this.config.locale });
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Ziyaretçi oturumu açar ya da mevcut olanı günceller.
|
|
58
|
+
*
|
|
59
|
+
* Sır saklanır; ikinci çağrı aynı ziyaretçiyi tazeler. Sunucu `VISITOR_INVALID`
|
|
60
|
+
* derse yerel kimlik silinir ve bir sonraki çağrı yeni oturum açar.
|
|
61
|
+
*/
|
|
62
|
+
async startSession(input = {}) {
|
|
63
|
+
const result = await this.request("POST", "/v1/sdk/chat/session", input);
|
|
64
|
+
const visitor = result.data?.visitor;
|
|
65
|
+
if (result.ok && visitor?.id && visitor.secret) {
|
|
66
|
+
await this.storeVisitor({
|
|
67
|
+
id: visitor.id,
|
|
68
|
+
secret: visitor.secret,
|
|
69
|
+
appKey: this.config.appKey,
|
|
70
|
+
name: visitor.name ?? null,
|
|
71
|
+
email: visitor.email ?? null
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
/** Oturum açmış kullanıcıyı ziyaretçiye bağlar (kişi kaydı upsert edilir). */
|
|
77
|
+
identify(input) {
|
|
78
|
+
return this.request("POST", "/v1/sdk/identify", input);
|
|
79
|
+
}
|
|
80
|
+
/** Saklanan ziyaretçi kimliği — yoksa `null`. */
|
|
81
|
+
async currentVisitor() {
|
|
82
|
+
const stored = await this.loadVisitor();
|
|
83
|
+
return stored ? { id: stored.id, name: stored.name, email: stored.email } : null;
|
|
84
|
+
}
|
|
85
|
+
/** Yerel kimliği siler: çıkış yapıldığında çağrılır. Sunucudaki kayıt kalır. */
|
|
86
|
+
async signOut() {
|
|
87
|
+
this.visitor = null;
|
|
88
|
+
this.loaded = true;
|
|
89
|
+
await this.storage.removeItem(STORAGE_KEY);
|
|
90
|
+
}
|
|
91
|
+
// ── Sohbet ────────────────────────────────────────────────────────────
|
|
92
|
+
listConversations() {
|
|
93
|
+
return this.request("GET", "/v1/sdk/chat/conversations");
|
|
94
|
+
}
|
|
95
|
+
getConversation(id, query) {
|
|
96
|
+
return this.request("GET", `/v1/sdk/chat/conversations/${enc(id)}`, void 0, query);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* İlk mesajla konuşma açar. Kota burada harcanır — konuşma başına sayılır,
|
|
100
|
+
* mesaj başına değil.
|
|
101
|
+
*/
|
|
102
|
+
startConversation(input) {
|
|
103
|
+
return this.request("POST", "/v1/sdk/chat/conversations", {
|
|
104
|
+
client_id: clientId(),
|
|
105
|
+
...input
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
sendMessage(conversationId, input) {
|
|
109
|
+
return this.request("POST", `/v1/sdk/chat/conversations/${enc(conversationId)}/messages`, {
|
|
110
|
+
client_id: clientId(),
|
|
111
|
+
...input
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/** Yalnız kendi mesajı ve gönderimden sonraki 15 dakika içinde. */
|
|
115
|
+
editMessage(conversationId, messageId, body) {
|
|
116
|
+
return this.request(
|
|
117
|
+
"PATCH",
|
|
118
|
+
`/v1/sdk/chat/conversations/${enc(conversationId)}/messages/${enc(messageId)}`,
|
|
119
|
+
{ body }
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
deleteMessage(conversationId, messageId) {
|
|
123
|
+
return this.request(
|
|
124
|
+
"DELETE",
|
|
125
|
+
`/v1/sdk/chat/conversations/${enc(conversationId)}/messages/${enc(messageId)}`
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
/** Aynı emoji ikinci kez gönderilirse tepki kaldırılır. */
|
|
129
|
+
reactToMessage(conversationId, messageId, emoji) {
|
|
130
|
+
return this.request(
|
|
131
|
+
"POST",
|
|
132
|
+
`/v1/sdk/chat/conversations/${enc(conversationId)}/messages/${enc(messageId)}/reactions`,
|
|
133
|
+
{ emoji }
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
setTyping(conversationId, isTyping) {
|
|
137
|
+
return this.request("POST", `/v1/sdk/chat/conversations/${enc(conversationId)}/typing`, {
|
|
138
|
+
is_typing: isTyping
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
markRead(conversationId, lastMessageId) {
|
|
142
|
+
return this.request("POST", `/v1/sdk/chat/conversations/${enc(conversationId)}/read`, {
|
|
143
|
+
last_message_id: lastMessageId
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Ek dosya yükler; dönen tanımlayıcı `sendMessage`'a `attachments` içinde
|
|
148
|
+
* verilir. İki adım olmasının sebebi: dosya yüklenirken mesaj metni hâlâ
|
|
149
|
+
* yazılıyor olabilir ve yarım kalan yükleme mesaj kaydı yaratmamalı.
|
|
150
|
+
*/
|
|
151
|
+
uploadAttachment(conversationId, file, fileName) {
|
|
152
|
+
const form = new FormData();
|
|
153
|
+
form.append("file", file, fileName);
|
|
154
|
+
return this.request(
|
|
155
|
+
"POST",
|
|
156
|
+
`/v1/sdk/chat/conversations/${enc(conversationId)}/attachments`,
|
|
157
|
+
form
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
closeConversation(conversationId) {
|
|
161
|
+
return this.request("POST", `/v1/sdk/chat/conversations/${enc(conversationId)}/close`);
|
|
162
|
+
}
|
|
163
|
+
rateConversation(conversationId, rating, comment) {
|
|
164
|
+
return this.request("POST", `/v1/sdk/chat/conversations/${enc(conversationId)}/rate`, {
|
|
165
|
+
rating,
|
|
166
|
+
comment
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
// ── Push ──────────────────────────────────────────────────────────────
|
|
170
|
+
/**
|
|
171
|
+
* Cihaz token'ını kaydeder. Token'ı almak (FCM/APNs/Web Push izni) ev
|
|
172
|
+
* sahibinin işidir; SDK yalnız iletir — izin diyaloğunu kimin, ne zaman
|
|
173
|
+
* göstereceği ürün kararıdır, kütüphane kararı değil.
|
|
174
|
+
*/
|
|
175
|
+
registerDevice(input) {
|
|
176
|
+
return this.request("POST", "/v1/sdk/devices", input);
|
|
177
|
+
}
|
|
178
|
+
/** Çıkışta çağrılır: kayıt silinmez, kapatılır (geçmiş korunur). */
|
|
179
|
+
unregisterDevice(token) {
|
|
180
|
+
return this.request("DELETE", `/v1/sdk/devices/${enc(token)}`);
|
|
181
|
+
}
|
|
182
|
+
// ── HTTP ──────────────────────────────────────────────────────────────
|
|
183
|
+
async request(method, path, body, query) {
|
|
184
|
+
const stored = await this.loadVisitor();
|
|
185
|
+
const isForm = typeof FormData !== "undefined" && body instanceof FormData;
|
|
186
|
+
const headers = {
|
|
187
|
+
Accept: "application/json",
|
|
188
|
+
"X-Signalbird-App-Key": this.config.appKey
|
|
189
|
+
};
|
|
190
|
+
if (stored?.secret) headers["X-Signalbird-Visitor"] = stored.secret;
|
|
191
|
+
if (body !== void 0 && !isForm) headers["Content-Type"] = "application/json";
|
|
192
|
+
if (this.config.locale) headers["X-Locale"] = this.config.locale;
|
|
193
|
+
const controller = new AbortController();
|
|
194
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
195
|
+
try {
|
|
196
|
+
const response = await this.doFetch(this.baseUrl + path + buildQuery(query), {
|
|
197
|
+
method,
|
|
198
|
+
headers,
|
|
199
|
+
body: body === void 0 ? void 0 : isForm ? body : JSON.stringify(body),
|
|
200
|
+
signal: controller.signal
|
|
201
|
+
});
|
|
202
|
+
const text = await response.text();
|
|
203
|
+
let data = null;
|
|
204
|
+
try {
|
|
205
|
+
data = text ? JSON.parse(text) : null;
|
|
206
|
+
} catch {
|
|
207
|
+
data = text;
|
|
208
|
+
}
|
|
209
|
+
if (response.ok) {
|
|
210
|
+
return { ok: true, status: response.status, data };
|
|
211
|
+
}
|
|
212
|
+
const code = data && typeof data === "object" && typeof data.code === "string" && data.code || (response.status === 422 ? "VALIDATION_ERROR" : `HTTP_${response.status}`);
|
|
213
|
+
if (code === "VISITOR_INVALID" || response.status === 401) {
|
|
214
|
+
await this.signOut();
|
|
215
|
+
}
|
|
216
|
+
if (this.config.debug) {
|
|
217
|
+
console.warn(`[signalbird] ${code} (HTTP ${response.status})`);
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
ok: false,
|
|
221
|
+
status: response.status,
|
|
222
|
+
code,
|
|
223
|
+
message: data && typeof data === "object" && typeof data.message === "string" && data.message || `HTTP ${response.status}`,
|
|
224
|
+
data
|
|
225
|
+
};
|
|
226
|
+
} catch (error) {
|
|
227
|
+
const timedOut = error instanceof Error && error.name === "AbortError";
|
|
228
|
+
if (this.config.debug) {
|
|
229
|
+
console.warn("[signalbird] ula\u015F\u0131lamad\u0131:", error);
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
ok: false,
|
|
233
|
+
status: 0,
|
|
234
|
+
code: timedOut ? "TIMEOUT" : "NETWORK_ERROR",
|
|
235
|
+
message: error instanceof Error ? error.message : "network error"
|
|
236
|
+
};
|
|
237
|
+
} finally {
|
|
238
|
+
clearTimeout(timer);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
async loadVisitor() {
|
|
242
|
+
if (this.loaded) return this.visitor;
|
|
243
|
+
this.loaded = true;
|
|
244
|
+
try {
|
|
245
|
+
const raw = await this.storage.getItem(STORAGE_KEY);
|
|
246
|
+
if (!raw) return null;
|
|
247
|
+
const parsed = JSON.parse(raw);
|
|
248
|
+
if (!parsed?.secret || parsed.appKey !== this.config.appKey) return null;
|
|
249
|
+
this.visitor = parsed;
|
|
250
|
+
} catch {
|
|
251
|
+
this.visitor = null;
|
|
252
|
+
}
|
|
253
|
+
return this.visitor;
|
|
254
|
+
}
|
|
255
|
+
async storeVisitor(visitor) {
|
|
256
|
+
this.visitor = visitor;
|
|
257
|
+
this.loaded = true;
|
|
258
|
+
try {
|
|
259
|
+
await this.storage.setItem(STORAGE_KEY, JSON.stringify(visitor));
|
|
260
|
+
} catch {
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
function enc(value) {
|
|
265
|
+
return encodeURIComponent(value);
|
|
266
|
+
}
|
|
267
|
+
function buildQuery(query) {
|
|
268
|
+
if (!query) return "";
|
|
269
|
+
const params = new URLSearchParams();
|
|
270
|
+
for (const [key, value] of Object.entries(query)) {
|
|
271
|
+
if (value === void 0 || value === null) continue;
|
|
272
|
+
params.append(key, String(value));
|
|
273
|
+
}
|
|
274
|
+
const encoded = params.toString();
|
|
275
|
+
return encoded ? `?${encoded}` : "";
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// src/app/session.ts
|
|
279
|
+
var IDLE_LADDER = [2e4, 2e4, 2e4, 6e4, 6e4, 18e4];
|
|
280
|
+
var ACTIVE_INTERVAL = 3e3;
|
|
281
|
+
var ChatSession = class {
|
|
282
|
+
constructor(app, options = {}) {
|
|
283
|
+
this.app = app;
|
|
284
|
+
this.options = options;
|
|
285
|
+
this.state = {
|
|
286
|
+
enabled: false,
|
|
287
|
+
loading: true,
|
|
288
|
+
conversation: null,
|
|
289
|
+
messages: [],
|
|
290
|
+
unread: 0,
|
|
291
|
+
agentTyping: false,
|
|
292
|
+
withinHours: true
|
|
293
|
+
};
|
|
294
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
295
|
+
this.timer = null;
|
|
296
|
+
this.step = 0;
|
|
297
|
+
this.stopped = false;
|
|
298
|
+
this.polling = false;
|
|
299
|
+
this.active = options.active ?? false;
|
|
300
|
+
}
|
|
301
|
+
// ── Abonelik ──────────────────────────────────────────────────────────
|
|
302
|
+
subscribe(listener) {
|
|
303
|
+
this.listeners.add(listener);
|
|
304
|
+
listener(this.state);
|
|
305
|
+
return () => this.listeners.delete(listener);
|
|
306
|
+
}
|
|
307
|
+
snapshot() {
|
|
308
|
+
return this.state;
|
|
309
|
+
}
|
|
310
|
+
// ── Yaşam döngüsü ─────────────────────────────────────────────────────
|
|
311
|
+
/** Bootstrap + varsa mevcut konuşmayı yükler, sonra yoklamayı başlatır. */
|
|
312
|
+
async start() {
|
|
313
|
+
this.stopped = false;
|
|
314
|
+
const boot = await this.app.bootstrap();
|
|
315
|
+
const app = boot.data?.app;
|
|
316
|
+
if (!boot.ok || !app?.chat_enabled) {
|
|
317
|
+
this.patch({ enabled: false, loading: false, errorCode: boot.code });
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
this.patch({ enabled: true, withinHours: app.within_hours ?? true });
|
|
321
|
+
if (await this.app.currentVisitor()) {
|
|
322
|
+
await this.refresh();
|
|
323
|
+
}
|
|
324
|
+
this.patch({ loading: false });
|
|
325
|
+
this.schedule();
|
|
326
|
+
}
|
|
327
|
+
/** Panel açıldı/kapandı — yoklama hızı buna göre değişir. */
|
|
328
|
+
setActive(active) {
|
|
329
|
+
if (this.active === active) return;
|
|
330
|
+
this.active = active;
|
|
331
|
+
this.step = 0;
|
|
332
|
+
if (active) void this.refresh();
|
|
333
|
+
this.schedule();
|
|
334
|
+
}
|
|
335
|
+
stop() {
|
|
336
|
+
this.stopped = true;
|
|
337
|
+
if (this.timer) clearTimeout(this.timer);
|
|
338
|
+
this.timer = null;
|
|
339
|
+
}
|
|
340
|
+
// ── Eylemler ──────────────────────────────────────────────────────────
|
|
341
|
+
/** Ön-form gönderildiğinde ya da uygulama kullanıcıyı tanıdığında. */
|
|
342
|
+
async openSession(input) {
|
|
343
|
+
const result = await this.app.startSession(input);
|
|
344
|
+
if (result.ok) await this.refresh();
|
|
345
|
+
return result;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Mesaj gönderir. Konuşma yoksa açar.
|
|
349
|
+
*
|
|
350
|
+
* İyimser: mesaj listeye ANINDA düşer, `client_id` ile eşlenir. Sunucu
|
|
351
|
+
* cevabı gelince yerel kopya onunla değiştirilir; başarısızsa `failed`
|
|
352
|
+
* işaretlenir ve arayüz "yeniden dene" gösterebilir.
|
|
353
|
+
*/
|
|
354
|
+
async send(body, attachments) {
|
|
355
|
+
const trimmed = body.trim();
|
|
356
|
+
if (!trimmed && !attachments?.length) {
|
|
357
|
+
return { ok: false, status: 0, code: "EMPTY_BODY", message: "Mesaj bo\u015F." };
|
|
358
|
+
}
|
|
359
|
+
const cid = clientId();
|
|
360
|
+
const optimistic = {
|
|
361
|
+
id: cid,
|
|
362
|
+
client_id: cid,
|
|
363
|
+
sender_type: "visitor",
|
|
364
|
+
body: trimmed,
|
|
365
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
366
|
+
};
|
|
367
|
+
this.patch({ messages: [...this.state.messages, optimistic] });
|
|
368
|
+
if (!await this.app.currentVisitor()) {
|
|
369
|
+
const session = await this.app.startSession(this.options.visitor ?? {});
|
|
370
|
+
if (!session.ok) return this.markFailed(cid, session);
|
|
371
|
+
}
|
|
372
|
+
const conversation = this.state.conversation;
|
|
373
|
+
const result = conversation ? await this.app.sendMessage(conversation.id, { body: trimmed, client_id: cid, attachments }) : await this.app.startConversation({ body: trimmed, client_id: cid, attachments });
|
|
374
|
+
if (!result.ok) return this.markFailed(cid, result);
|
|
375
|
+
await this.refresh();
|
|
376
|
+
this.step = 0;
|
|
377
|
+
this.schedule();
|
|
378
|
+
return result;
|
|
379
|
+
}
|
|
380
|
+
/** İlk tuşta `true`, 2.5 s hareketsizlikte `false` — çağıran zamanlar. */
|
|
381
|
+
typing(isTyping) {
|
|
382
|
+
const conversation = this.state.conversation;
|
|
383
|
+
if (!conversation) return;
|
|
384
|
+
void this.app.setTyping(conversation.id, isTyping);
|
|
385
|
+
}
|
|
386
|
+
/** Görülen son mesaja kadar okundu işaretler. */
|
|
387
|
+
async markRead() {
|
|
388
|
+
const conversation = this.state.conversation;
|
|
389
|
+
const last = this.state.messages[this.state.messages.length - 1];
|
|
390
|
+
if (!conversation || !last) return;
|
|
391
|
+
await this.app.markRead(conversation.id, last.id);
|
|
392
|
+
this.patch({ unread: 0 });
|
|
393
|
+
}
|
|
394
|
+
async close(rating, comment) {
|
|
395
|
+
const conversation = this.state.conversation;
|
|
396
|
+
if (!conversation) return;
|
|
397
|
+
if (typeof rating === "number") {
|
|
398
|
+
await this.app.rateConversation(conversation.id, rating, comment);
|
|
399
|
+
}
|
|
400
|
+
await this.app.closeConversation(conversation.id);
|
|
401
|
+
await this.refresh();
|
|
402
|
+
}
|
|
403
|
+
/** Sunucudaki durumu çeker; imleç varsa yalnız yenileri ister. */
|
|
404
|
+
async refresh() {
|
|
405
|
+
const current = this.state.conversation;
|
|
406
|
+
if (!current) {
|
|
407
|
+
const list = await this.app.listConversations();
|
|
408
|
+
const first = list.data?.data?.[0];
|
|
409
|
+
if (!first) {
|
|
410
|
+
this.patch({ errorCode: list.ok ? void 0 : list.code });
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
const detail2 = await this.app.getConversation(first.id);
|
|
414
|
+
this.applyConversation(detail2.data?.conversation ?? first, true);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
const after = this.lastServerMessageId();
|
|
418
|
+
const detail = await this.app.getConversation(current.id, after ? { after } : void 0);
|
|
419
|
+
if (!detail.ok || !detail.data?.conversation) {
|
|
420
|
+
this.patch({ errorCode: detail.code });
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
this.applyConversation(detail.data.conversation, !after);
|
|
424
|
+
}
|
|
425
|
+
// ── İç işler ──────────────────────────────────────────────────────────
|
|
426
|
+
applyConversation(conversation, replace) {
|
|
427
|
+
const incoming = conversation.messages ?? [];
|
|
428
|
+
const merged = replace ? incoming : mergeMessages(this.state.messages, incoming);
|
|
429
|
+
if (incoming.length) this.step = 0;
|
|
430
|
+
this.patch({
|
|
431
|
+
conversation,
|
|
432
|
+
messages: merged,
|
|
433
|
+
unread: conversation.unread_count ?? 0,
|
|
434
|
+
agentTyping: !!conversation.agent_typing,
|
|
435
|
+
withinHours: conversation.within_hours ?? this.state.withinHours,
|
|
436
|
+
errorCode: void 0
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
/** İyimser kayıtlar sunucu kimliği taşımaz; imleç yalnız gerçek kimliktir. */
|
|
440
|
+
lastServerMessageId() {
|
|
441
|
+
for (let i = this.state.messages.length - 1; i >= 0; i--) {
|
|
442
|
+
const message = this.state.messages[i];
|
|
443
|
+
if (message.id && !message.id.startsWith("c_") && message.id !== message.client_id) {
|
|
444
|
+
return message.id;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return void 0;
|
|
448
|
+
}
|
|
449
|
+
markFailed(cid, result) {
|
|
450
|
+
this.patch({
|
|
451
|
+
messages: this.state.messages.map(
|
|
452
|
+
(m) => m.client_id === cid ? { ...m, failed: true } : m
|
|
453
|
+
),
|
|
454
|
+
errorCode: result.code
|
|
455
|
+
});
|
|
456
|
+
return result;
|
|
457
|
+
}
|
|
458
|
+
schedule() {
|
|
459
|
+
if (this.timer) clearTimeout(this.timer);
|
|
460
|
+
if (this.stopped) return;
|
|
461
|
+
const delay = this.active ? ACTIVE_INTERVAL : IDLE_LADDER[Math.min(this.step, IDLE_LADDER.length - 1)];
|
|
462
|
+
this.timer = setTimeout(() => void this.tick(), delay);
|
|
463
|
+
}
|
|
464
|
+
async tick() {
|
|
465
|
+
if (this.stopped || this.polling) return;
|
|
466
|
+
if (!this.visible()) {
|
|
467
|
+
this.schedule();
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
this.polling = true;
|
|
471
|
+
try {
|
|
472
|
+
await this.refresh();
|
|
473
|
+
} finally {
|
|
474
|
+
this.polling = false;
|
|
475
|
+
}
|
|
476
|
+
if (!this.active) this.step++;
|
|
477
|
+
this.schedule();
|
|
478
|
+
}
|
|
479
|
+
visible() {
|
|
480
|
+
if (this.options.isVisible) return this.options.isVisible();
|
|
481
|
+
try {
|
|
482
|
+
if (typeof document !== "undefined" && document.visibilityState) {
|
|
483
|
+
return document.visibilityState === "visible";
|
|
484
|
+
}
|
|
485
|
+
} catch {
|
|
486
|
+
}
|
|
487
|
+
return true;
|
|
488
|
+
}
|
|
489
|
+
patch(partial) {
|
|
490
|
+
this.state = { ...this.state, ...partial };
|
|
491
|
+
for (const listener of this.listeners) {
|
|
492
|
+
try {
|
|
493
|
+
listener(this.state);
|
|
494
|
+
} catch (error) {
|
|
495
|
+
console.warn("[signalbird] dinleyici hatas\u0131:", error);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
function mergeMessages(existing, incoming) {
|
|
501
|
+
if (!incoming.length) return existing;
|
|
502
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
503
|
+
for (const message of existing) {
|
|
504
|
+
byKey.set(message.client_id ?? message.id, message);
|
|
505
|
+
}
|
|
506
|
+
for (const message of incoming) {
|
|
507
|
+
const key = message.client_id ?? message.id;
|
|
508
|
+
byKey.delete(key);
|
|
509
|
+
byKey.set(key, message);
|
|
510
|
+
}
|
|
511
|
+
return [...byKey.values()].sort((a, b) => (a.created_at ?? "").localeCompare(b.created_at ?? ""));
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
exports.ChatSession = ChatSession;
|
|
515
|
+
exports.SignalbirdApp = SignalbirdApp;
|
|
516
|
+
exports.clientId = clientId;
|
|
517
|
+
//# sourceMappingURL=app.js.map
|
|
518
|
+
//# sourceMappingURL=app.js.map
|
package/dist/app.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/app/client.ts","../src/app/session.ts"],"names":["detail"],"mappings":"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","file":"app.js","sourcesContent":["/**\n * Uygulama istemcisi — son kullanıcı tarafı (sohbet + push kaydı).\n *\n * Tek bir sınıf; tarayıcı, React Native, Electron ve test aynı gövdeyi kullanır.\n * Platform farkı iki noktada toplanmıştır ve ikisi de dışarıdan verilir:\n * `storage` (ziyaretçi sırrı nerede durur) ve `fetchImpl`. Çatıya özel sarmalayıcı\n * yazmak yerine bunu seçtik — React, Vue, Angular ve RN uyarlamaları bu sınıfın\n * ÜSTÜNE oturur, kopyası değildir.\n *\n * Kimlik iki parçadır: açık uygulama anahtarı (`X-Signalbird-App-Key`) ve\n * ziyaretçi sırrı (`X-Signalbird-Visitor`). Sır yalnız oturum açılışında döner;\n * kaybolursa yeni oturum açılır ve geçmiş konuşmalar görünmez — bu yüzden\n * saklama katmanı zorunludur, isteğe bağlı değil.\n *\n * Hiçbir metot istisna fırlatmaz: sohbet balonunun hatası müşterinin ödeme\n * sayfasını çökertmemeli. Sonuç her zaman `{ok, status, …}` zarfıdır.\n *\n * Sözleşme: docs/CONTRACT.md § 11\n */\nimport type {\n AppConfig,\n AppStorage,\n BootstrapResult,\n Conversation,\n ConversationQuery,\n IdentifyInput,\n Message,\n RegisterDeviceInput,\n SbResult,\n SendMessageInput,\n SessionInput,\n StartConversationInput,\n Visitor,\n} from './types';\n\nconst DEFAULT_BASE_URL = 'https://signalbird.io/api';\nconst STORAGE_KEY = 'sb_visitor';\n\ninterface StoredVisitor {\n id: string;\n secret: string;\n appKey: string;\n name?: string | null;\n email?: string | null;\n}\n\n/** Depo verilmezse: tarayıcıda localStorage, başka yerde bellek. */\nfunction defaultStorage(): AppStorage {\n try {\n if (typeof localStorage !== 'undefined') {\n return {\n getItem: (k) => localStorage.getItem(k),\n setItem: (k, v) => localStorage.setItem(k, v),\n removeItem: (k) => localStorage.removeItem(k),\n };\n }\n } catch {\n // Gizli sekme / kısıtlı iframe — belleğe düş.\n }\n\n const memory = new Map<string, string>();\n\n return {\n getItem: (k) => memory.get(k) ?? null,\n setItem: (k, v) => void memory.set(k, v),\n removeItem: (k) => void memory.delete(k),\n };\n}\n\n/** RFC 4122 uyumlu olmak zorunda değil; tek işi yerel kopyayı eşlemek. */\nexport function clientId(): string {\n try {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n } catch {\n /* yok say */\n }\n\n return `c_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;\n}\n\nexport class SignalbirdApp {\n private readonly baseUrl: string;\n private readonly storage: AppStorage;\n private readonly timeout: number;\n private readonly doFetch: typeof fetch;\n private visitor: StoredVisitor | null = null;\n private loaded = false;\n\n constructor(private readonly config: AppConfig) {\n if (!config?.appKey) {\n throw new Error('Signalbird: appKey zorunlu (sbw_pub_…).');\n }\n\n // Takım anahtarı istemciye gömülürse tüm gönderim yetkisi sızar. Sunucu da\n // reddederdi ama o noktada anahtar çoktan yayınlanmış olurdu.\n if (!config.appKey.startsWith('sbw_pub_')) {\n throw new Error(\n 'Signalbird: uygulama istemcisi açık uygulama anahtarı ister (sbw_pub_…). ' +\n 'Takım anahtarını (sb_…) istemci koduna KOYMAYIN.'\n );\n }\n\n this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, '');\n this.storage = config.storage ?? defaultStorage();\n this.timeout = config.timeout ?? 10000;\n this.doFetch = config.fetchImpl ?? ((...args) => fetch(...args));\n }\n\n // ── Kimlik ────────────────────────────────────────────────────────────\n\n /** Uygulama ayarları: sohbet açık mı, renk, çalışma saati, ön-form. */\n bootstrap(): Promise<SbResult<BootstrapResult>> {\n return this.request('POST', '/v1/sdk/bootstrap', { locale: this.config.locale });\n }\n\n /**\n * Ziyaretçi oturumu açar ya da mevcut olanı günceller.\n *\n * Sır saklanır; ikinci çağrı aynı ziyaretçiyi tazeler. Sunucu `VISITOR_INVALID`\n * derse yerel kimlik silinir ve bir sonraki çağrı yeni oturum açar.\n */\n async startSession(input: SessionInput = {}): Promise<SbResult<{ visitor: Visitor }>> {\n const result = await this.request<{ visitor: Visitor }>('POST', '/v1/sdk/chat/session', input);\n\n const visitor = result.data?.visitor;\n\n if (result.ok && visitor?.id && visitor.secret) {\n await this.storeVisitor({\n id: visitor.id,\n secret: visitor.secret,\n appKey: this.config.appKey,\n name: visitor.name ?? null,\n email: visitor.email ?? null,\n });\n }\n\n return result;\n }\n\n /** Oturum açmış kullanıcıyı ziyaretçiye bağlar (kişi kaydı upsert edilir). */\n identify(input: IdentifyInput): Promise<SbResult<{ visitor: Visitor }>> {\n return this.request('POST', '/v1/sdk/identify', input);\n }\n\n /** Saklanan ziyaretçi kimliği — yoksa `null`. */\n async currentVisitor(): Promise<{ id: string; name?: string | null; email?: string | null } | null> {\n const stored = await this.loadVisitor();\n\n return stored ? { id: stored.id, name: stored.name, email: stored.email } : null;\n }\n\n /** Yerel kimliği siler: çıkış yapıldığında çağrılır. Sunucudaki kayıt kalır. */\n async signOut(): Promise<void> {\n this.visitor = null;\n this.loaded = true;\n await this.storage.removeItem(STORAGE_KEY);\n }\n\n // ── Sohbet ────────────────────────────────────────────────────────────\n\n listConversations(): Promise<SbResult<{ data: Conversation[] }>> {\n return this.request('GET', '/v1/sdk/chat/conversations');\n }\n\n getConversation(id: string, query?: ConversationQuery): Promise<SbResult<{ conversation: Conversation }>> {\n return this.request('GET', `/v1/sdk/chat/conversations/${enc(id)}`, undefined, query);\n }\n\n /**\n * İlk mesajla konuşma açar. Kota burada harcanır — konuşma başına sayılır,\n * mesaj başına değil.\n */\n startConversation(input: StartConversationInput): Promise<SbResult<{ conversation: Conversation; message: Message }>> {\n return this.request('POST', '/v1/sdk/chat/conversations', {\n client_id: clientId(),\n ...input,\n });\n }\n\n sendMessage(conversationId: string, input: SendMessageInput): Promise<SbResult<{ message: Message }>> {\n return this.request('POST', `/v1/sdk/chat/conversations/${enc(conversationId)}/messages`, {\n client_id: clientId(),\n ...input,\n });\n }\n\n /** Yalnız kendi mesajı ve gönderimden sonraki 15 dakika içinde. */\n editMessage(conversationId: string, messageId: string, body: string): Promise<SbResult<{ message: Message }>> {\n return this.request(\n 'PATCH',\n `/v1/sdk/chat/conversations/${enc(conversationId)}/messages/${enc(messageId)}`,\n { body }\n );\n }\n\n deleteMessage(conversationId: string, messageId: string): Promise<SbResult<unknown>> {\n return this.request(\n 'DELETE',\n `/v1/sdk/chat/conversations/${enc(conversationId)}/messages/${enc(messageId)}`\n );\n }\n\n /** Aynı emoji ikinci kez gönderilirse tepki kaldırılır. */\n reactToMessage(conversationId: string, messageId: string, emoji: string): Promise<SbResult<{ message: Message }>> {\n return this.request(\n 'POST',\n `/v1/sdk/chat/conversations/${enc(conversationId)}/messages/${enc(messageId)}/reactions`,\n { emoji }\n );\n }\n\n setTyping(conversationId: string, isTyping: boolean): Promise<SbResult<unknown>> {\n return this.request('POST', `/v1/sdk/chat/conversations/${enc(conversationId)}/typing`, {\n is_typing: isTyping,\n });\n }\n\n markRead(conversationId: string, lastMessageId?: string): Promise<SbResult<unknown>> {\n return this.request('POST', `/v1/sdk/chat/conversations/${enc(conversationId)}/read`, {\n last_message_id: lastMessageId,\n });\n }\n\n /**\n * Ek dosya yükler; dönen tanımlayıcı `sendMessage`'a `attachments` içinde\n * verilir. İki adım olmasının sebebi: dosya yüklenirken mesaj metni hâlâ\n * yazılıyor olabilir ve yarım kalan yükleme mesaj kaydı yaratmamalı.\n */\n uploadAttachment(conversationId: string, file: unknown, fileName?: string): Promise<SbResult<{ attachment: unknown }>> {\n const form = new FormData();\n form.append('file', file as Blob, fileName);\n\n return this.request(\n 'POST',\n `/v1/sdk/chat/conversations/${enc(conversationId)}/attachments`,\n form\n );\n }\n\n closeConversation(conversationId: string): Promise<SbResult<{ conversation: Conversation }>> {\n return this.request('POST', `/v1/sdk/chat/conversations/${enc(conversationId)}/close`);\n }\n\n rateConversation(conversationId: string, rating: number, comment?: string): Promise<SbResult<unknown>> {\n return this.request('POST', `/v1/sdk/chat/conversations/${enc(conversationId)}/rate`, {\n rating,\n comment,\n });\n }\n\n // ── Push ──────────────────────────────────────────────────────────────\n\n /**\n * Cihaz token'ını kaydeder. Token'ı almak (FCM/APNs/Web Push izni) ev\n * sahibinin işidir; SDK yalnız iletir — izin diyaloğunu kimin, ne zaman\n * göstereceği ürün kararıdır, kütüphane kararı değil.\n */\n registerDevice(input: RegisterDeviceInput): Promise<SbResult<unknown>> {\n return this.request('POST', '/v1/sdk/devices', input);\n }\n\n /** Çıkışta çağrılır: kayıt silinmez, kapatılır (geçmiş korunur). */\n unregisterDevice(token: string): Promise<SbResult<unknown>> {\n return this.request('DELETE', `/v1/sdk/devices/${enc(token)}`);\n }\n\n // ── HTTP ──────────────────────────────────────────────────────────────\n\n private async request<T>(\n method: 'GET' | 'POST' | 'PATCH' | 'DELETE',\n path: string,\n body?: unknown,\n query?: object\n ): Promise<SbResult<T>> {\n const stored = await this.loadVisitor();\n const isForm = typeof FormData !== 'undefined' && body instanceof FormData;\n\n const headers: Record<string, string> = {\n Accept: 'application/json',\n 'X-Signalbird-App-Key': this.config.appKey,\n };\n\n if (stored?.secret) headers['X-Signalbird-Visitor'] = stored.secret;\n if (body !== undefined && !isForm) headers['Content-Type'] = 'application/json';\n if (this.config.locale) headers['X-Locale'] = this.config.locale;\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await this.doFetch(this.baseUrl + path + buildQuery(query), {\n method,\n headers,\n body: body === undefined ? undefined : isForm ? (body as BodyInit) : JSON.stringify(body),\n signal: controller.signal,\n });\n\n const text = await response.text();\n let data: any = null;\n\n try {\n data = text ? JSON.parse(text) : null;\n } catch {\n data = text;\n }\n\n if (response.ok) {\n return { ok: true, status: response.status, data: data as T };\n }\n\n const code: string =\n (data && typeof data === 'object' && typeof data.code === 'string' && data.code) ||\n (response.status === 422 ? 'VALIDATION_ERROR' : `HTTP_${response.status}`);\n\n // Sır geçersizse yerel kimliği at: bir sonraki çağrı yeni oturum açar.\n // Aksi hâlde ziyaretçi sonsuza kadar 401 alırdı ve sohbet sessizce ölürdü.\n if (code === 'VISITOR_INVALID' || response.status === 401) {\n await this.signOut();\n }\n\n if (this.config.debug) {\n console.warn(`[signalbird] ${code} (HTTP ${response.status})`);\n }\n\n return {\n ok: false,\n status: response.status,\n code,\n message:\n (data && typeof data === 'object' && typeof data.message === 'string' && data.message) ||\n `HTTP ${response.status}`,\n data: data as T,\n };\n } catch (error) {\n const timedOut = error instanceof Error && error.name === 'AbortError';\n\n if (this.config.debug) {\n console.warn('[signalbird] ulaşılamadı:', error);\n }\n\n return {\n ok: false,\n status: 0,\n code: timedOut ? 'TIMEOUT' : 'NETWORK_ERROR',\n message: error instanceof Error ? error.message : 'network error',\n };\n } finally {\n clearTimeout(timer);\n }\n }\n\n private async loadVisitor(): Promise<StoredVisitor | null> {\n if (this.loaded) return this.visitor;\n\n this.loaded = true;\n\n try {\n const raw = await this.storage.getItem(STORAGE_KEY);\n if (!raw) return null;\n\n const parsed = JSON.parse(raw) as StoredVisitor;\n\n // Anahtar değiştiyse (uygulama döndürüldü, farklı ortam) kimlik geçersizdir.\n if (!parsed?.secret || parsed.appKey !== this.config.appKey) return null;\n\n this.visitor = parsed;\n } catch {\n this.visitor = null;\n }\n\n return this.visitor;\n }\n\n private async storeVisitor(visitor: StoredVisitor): Promise<void> {\n this.visitor = visitor;\n this.loaded = true;\n\n try {\n await this.storage.setItem(STORAGE_KEY, JSON.stringify(visitor));\n } catch {\n // Depo yazamıyorsa (kota, gizli sekme) oturum bu sekmede yaşar.\n }\n }\n}\n\nfunction enc(value: string): string {\n return encodeURIComponent(value);\n}\n\nfunction buildQuery(query: object | undefined): string {\n if (!query) return '';\n\n const params = new URLSearchParams();\n\n for (const [key, value] of Object.entries(query as Record<string, unknown>)) {\n if (value === undefined || value === null) continue;\n params.append(key, String(value));\n }\n\n const encoded = params.toString();\n\n return encoded ? `?${encoded}` : '';\n}\n","/**\n * Sohbet oturumu — çatısız durum yönetimi.\n *\n * `SignalbirdApp` ham uçları verir; burası bir sohbet ekranının gerçekten\n * ihtiyaç duyduğu şeyi verir: mesaj listesi, okunmamış sayısı, yazıyor durumu,\n * iyimser gönderim ve yoklama merdiveni. React/Vue/Angular/React Native\n * uyarlamaları bu sınıfa abone olur — üçünde de aynı mantığı yeniden yazmak,\n * üç ayrı hata takımı üretmek demekti.\n *\n * Yoklama merdiveni (penyu deseni): panel açıkken 3 s, kapalıyken 20 s ×3 →\n * 60 s ×2 → 180 s. Yeni veri merdiveni sıfırlar; sekme/uygulama arka plandayken\n * tur atlanır. WebSocket yoktur: imleçli yoklama, bağlantı kopmasında kendi\n * kendini toparlar ve mobil ağda pil yakmaz.\n */\nimport { clientId, SignalbirdApp } from './client';\nimport type { Conversation, Message, SbResult, SessionInput, StartConversationInput } from './types';\n\nexport interface ChatState {\n /** Sohbet bu uygulamada açık mı (`bootstrap` cevabı). */\n enabled: boolean;\n loading: boolean;\n conversation: Conversation | null;\n messages: Message[];\n unread: number;\n agentTyping: boolean;\n withinHours: boolean;\n /** Son hatanın kodu — arayüz isterse gösterir, göstermezse yutar. */\n errorCode?: string;\n}\n\nexport type ChatListener = (state: ChatState) => void;\n\nexport interface ChatSessionOptions {\n /** Panel açık mı — yoklama hızını belirler. */\n active?: boolean;\n /** Arka plandayken tur atlanır; varsayılan: `document.visibilityState`. */\n isVisible?: () => boolean;\n /** Açılışta oturum kurulurken kullanılacak ziyaretçi bilgisi. */\n visitor?: SessionInput;\n}\n\n/** Kapalı panelde yoklama aralıkları (ms) — son değer sonsuza kadar tekrarlar. */\nconst IDLE_LADDER = [20_000, 20_000, 20_000, 60_000, 60_000, 180_000];\nconst ACTIVE_INTERVAL = 3_000;\n\nexport class ChatSession {\n private state: ChatState = {\n enabled: false,\n loading: true,\n conversation: null,\n messages: [],\n unread: 0,\n agentTyping: false,\n withinHours: true,\n };\n\n private listeners = new Set<ChatListener>();\n private timer: ReturnType<typeof setTimeout> | null = null;\n private step = 0;\n private active: boolean;\n private stopped = false;\n private polling = false;\n\n constructor(\n private readonly app: SignalbirdApp,\n private readonly options: ChatSessionOptions = {}\n ) {\n this.active = options.active ?? false;\n }\n\n // ── Abonelik ──────────────────────────────────────────────────────────\n\n subscribe(listener: ChatListener): () => void {\n this.listeners.add(listener);\n listener(this.state);\n\n return () => this.listeners.delete(listener);\n }\n\n snapshot(): ChatState {\n return this.state;\n }\n\n // ── Yaşam döngüsü ─────────────────────────────────────────────────────\n\n /** Bootstrap + varsa mevcut konuşmayı yükler, sonra yoklamayı başlatır. */\n async start(): Promise<void> {\n this.stopped = false;\n\n const boot = await this.app.bootstrap();\n const app = boot.data?.app;\n\n if (!boot.ok || !app?.chat_enabled) {\n this.patch({ enabled: false, loading: false, errorCode: boot.code });\n\n return;\n }\n\n this.patch({ enabled: true, withinHours: app.within_hours ?? true });\n\n // Ziyaretçi yoksa oturum AÇILMAZ: ilk mesaja kadar bekleriz. Her sayfa\n // görüntülemesinde ziyaretçi kaydı açmak, hiç konuşmayacak binlerce boş\n // kayıt üretirdi.\n if (await this.app.currentVisitor()) {\n await this.refresh();\n }\n\n this.patch({ loading: false });\n this.schedule();\n }\n\n /** Panel açıldı/kapandı — yoklama hızı buna göre değişir. */\n setActive(active: boolean): void {\n if (this.active === active) return;\n\n this.active = active;\n this.step = 0;\n\n if (active) void this.refresh();\n\n this.schedule();\n }\n\n stop(): void {\n this.stopped = true;\n\n if (this.timer) clearTimeout(this.timer);\n\n this.timer = null;\n }\n\n // ── Eylemler ──────────────────────────────────────────────────────────\n\n /** Ön-form gönderildiğinde ya da uygulama kullanıcıyı tanıdığında. */\n async openSession(input: SessionInput): Promise<SbResult<unknown>> {\n const result = await this.app.startSession(input);\n\n if (result.ok) await this.refresh();\n\n return result;\n }\n\n /**\n * Mesaj gönderir. Konuşma yoksa açar.\n *\n * İyimser: mesaj listeye ANINDA düşer, `client_id` ile eşlenir. Sunucu\n * cevabı gelince yerel kopya onunla değiştirilir; başarısızsa `failed`\n * işaretlenir ve arayüz \"yeniden dene\" gösterebilir.\n */\n async send(body: string, attachments?: unknown[]): Promise<SbResult<unknown>> {\n const trimmed = body.trim();\n\n if (!trimmed && !attachments?.length) {\n return { ok: false, status: 0, code: 'EMPTY_BODY', message: 'Mesaj boş.' };\n }\n\n const cid = clientId();\n const optimistic: Message & { failed?: boolean } = {\n id: cid,\n client_id: cid,\n sender_type: 'visitor',\n body: trimmed,\n created_at: new Date().toISOString(),\n };\n\n this.patch({ messages: [...this.state.messages, optimistic] });\n\n // Ziyaretçi yoksa önce oturum: ilk mesaj kimliği de yaratır.\n if (!(await this.app.currentVisitor())) {\n const session = await this.app.startSession(this.options.visitor ?? {});\n\n if (!session.ok) return this.markFailed(cid, session);\n }\n\n const conversation = this.state.conversation;\n\n const result = conversation\n ? await this.app.sendMessage(conversation.id, { body: trimmed, client_id: cid, attachments })\n : await this.app.startConversation({ body: trimmed, client_id: cid, attachments } as StartConversationInput);\n\n if (!result.ok) return this.markFailed(cid, result);\n\n await this.refresh();\n this.step = 0;\n this.schedule();\n\n return result;\n }\n\n /** İlk tuşta `true`, 2.5 s hareketsizlikte `false` — çağıran zamanlar. */\n typing(isTyping: boolean): void {\n const conversation = this.state.conversation;\n\n if (!conversation) return;\n\n void this.app.setTyping(conversation.id, isTyping);\n }\n\n /** Görülen son mesaja kadar okundu işaretler. */\n async markRead(): Promise<void> {\n const conversation = this.state.conversation;\n const last = this.state.messages[this.state.messages.length - 1];\n\n if (!conversation || !last) return;\n\n await this.app.markRead(conversation.id, last.id);\n this.patch({ unread: 0 });\n }\n\n async close(rating?: number, comment?: string): Promise<void> {\n const conversation = this.state.conversation;\n\n if (!conversation) return;\n\n if (typeof rating === 'number') {\n await this.app.rateConversation(conversation.id, rating, comment);\n }\n\n await this.app.closeConversation(conversation.id);\n await this.refresh();\n }\n\n /** Sunucudaki durumu çeker; imleç varsa yalnız yenileri ister. */\n async refresh(): Promise<void> {\n const current = this.state.conversation;\n\n if (!current) {\n const list = await this.app.listConversations();\n const first = list.data?.data?.[0];\n\n if (!first) {\n this.patch({ errorCode: list.ok ? undefined : list.code });\n\n return;\n }\n\n const detail = await this.app.getConversation(first.id);\n\n this.applyConversation(detail.data?.conversation ?? first, true);\n\n return;\n }\n\n const after = this.lastServerMessageId();\n const detail = await this.app.getConversation(current.id, after ? { after } : undefined);\n\n if (!detail.ok || !detail.data?.conversation) {\n this.patch({ errorCode: detail.code });\n\n return;\n }\n\n this.applyConversation(detail.data.conversation, !after);\n }\n\n // ── İç işler ──────────────────────────────────────────────────────────\n\n private applyConversation(conversation: Conversation, replace: boolean): void {\n const incoming = conversation.messages ?? [];\n const merged = replace ? incoming : mergeMessages(this.state.messages, incoming);\n\n // Yeni veri geldiyse merdiven sıfırlanır: konuşma canlandığında bir sonraki\n // yoklamayı üç dakika beklemek, kullanıcıyı terk edilmiş hissettirir.\n if (incoming.length) this.step = 0;\n\n this.patch({\n conversation,\n messages: merged,\n unread: conversation.unread_count ?? 0,\n agentTyping: !!conversation.agent_typing,\n withinHours: conversation.within_hours ?? this.state.withinHours,\n errorCode: undefined,\n });\n }\n\n /** İyimser kayıtlar sunucu kimliği taşımaz; imleç yalnız gerçek kimliktir. */\n private lastServerMessageId(): string | undefined {\n for (let i = this.state.messages.length - 1; i >= 0; i--) {\n const message = this.state.messages[i];\n\n if (message.id && !message.id.startsWith('c_') && message.id !== message.client_id) {\n return message.id;\n }\n }\n\n return undefined;\n }\n\n private markFailed(cid: string, result: SbResult<unknown>): SbResult<unknown> {\n this.patch({\n messages: this.state.messages.map((m) =>\n m.client_id === cid ? { ...m, failed: true } : m\n ) as Message[],\n errorCode: result.code,\n });\n\n return result;\n }\n\n private schedule(): void {\n if (this.timer) clearTimeout(this.timer);\n if (this.stopped) return;\n\n const delay = this.active\n ? ACTIVE_INTERVAL\n : IDLE_LADDER[Math.min(this.step, IDLE_LADDER.length - 1)];\n\n this.timer = setTimeout(() => void this.tick(), delay);\n }\n\n private async tick(): Promise<void> {\n if (this.stopped || this.polling) return;\n\n // Arka plandaki sekme/uygulama tur atlar: görünmeyen sohbet için ağ\n // trafiği üretmek pil ve kota harcar.\n if (!this.visible()) {\n this.schedule();\n\n return;\n }\n\n this.polling = true;\n\n try {\n await this.refresh();\n } finally {\n this.polling = false;\n }\n\n if (!this.active) this.step++;\n\n this.schedule();\n }\n\n private visible(): boolean {\n if (this.options.isVisible) return this.options.isVisible();\n\n try {\n if (typeof document !== 'undefined' && document.visibilityState) {\n return document.visibilityState === 'visible';\n }\n } catch {\n /* yok say */\n }\n\n return true;\n }\n\n private patch(partial: Partial<ChatState>): void {\n this.state = { ...this.state, ...partial };\n\n for (const listener of this.listeners) {\n try {\n listener(this.state);\n } catch (error) {\n // Bir dinleyicinin hatası diğerlerini ve oturumu düşürmemeli.\n console.warn('[signalbird] dinleyici hatası:', error);\n }\n }\n }\n}\n\n/** Sunucu kaydı, aynı `client_id`'li yerel kopyanın yerine geçer. */\nfunction mergeMessages(existing: Message[], incoming: Message[]): Message[] {\n if (!incoming.length) return existing;\n\n const byKey = new Map<string, Message>();\n\n for (const message of existing) {\n byKey.set(message.client_id ?? message.id, message);\n }\n\n for (const message of incoming) {\n const key = message.client_id ?? message.id;\n byKey.delete(key);\n byKey.set(key, message);\n }\n\n return [...byKey.values()].sort((a, b) => (a.created_at ?? '').localeCompare(b.created_at ?? ''));\n}\n"]}
|