@chatlychat/sdk-js 0.0.0-stage → 0.1.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/dist/index.js ADDED
@@ -0,0 +1,501 @@
1
+ /**
2
+ * Browser SDK. Pairs with the widget loader to give product code a way to
3
+ * identify visitors, fire CDP events, and control the widget.
4
+ *
5
+ * import { Chatly } from '@chatlychat/sdk-js';
6
+ * const lc = Chatly.init({ publicId: 'acme-web', apiUrl: 'https://api.acme.com' });
7
+ * await lc.identify({ email: 'a@b.com', name: 'Alice' });
8
+ * await lc.track('signup_completed', { plan: 'pro' });
9
+ * lc.open();
10
+ */
11
+ import { createTypingTracker, newMessagesSince, typingActorOf } from './live.js';
12
+ export { TYPING_STOP_AFTER_MS } from './live.js';
13
+ export class Chatly {
14
+ opts;
15
+ base;
16
+ visitorToken;
17
+ /**
18
+ * The realtime host `/v1/widget/init` reported, remembered for `subscribe`.
19
+ *
20
+ * `/v1/widget/realtime/token` returns `{ token, expiresAt }` and NO url —
21
+ * the first version of `subscribe` read a `url` off it and silently fell
22
+ * back to the API host, which is not where the socket server is.
23
+ */
24
+ realtimeUrl = null;
25
+ /**
26
+ * This visitor's contact id, remembered for the typing event — the realtime
27
+ * gateway's payload requires an `actorId` and the visitor's is their contact.
28
+ */
29
+ contactId = null;
30
+ static init(opts) {
31
+ return new Chatly(opts);
32
+ }
33
+ constructor(opts) {
34
+ this.opts = opts;
35
+ /*
36
+ * Production, not a dev hostname.
37
+ *
38
+ * This was `https://api.livechat.local`, which resolves nowhere — so a
39
+ * developer who followed the published docs and omitted `apiUrl` got an
40
+ * SDK whose every call failed, and the docs page carried a warning
41
+ * telling each reader to override it. Reported by a customer who
42
+ * integrated from the docs alone, not caught here, because every test in
43
+ * this package passes an explicit `apiUrl`.
44
+ *
45
+ * A self-hosted install still overrides it; that is the only caller that
46
+ * ever should.
47
+ */
48
+ this.base = opts.apiUrl ?? 'https://api.chatlychat.com';
49
+ // Storage key deliberately left as `livechat:` — the internal scope name.
50
+ // Renaming it would orphan the visitor token of every browser that has
51
+ // one, silently signing those visitors out of their own conversation.
52
+ // Cosmetics are not worth that; the customer-visible strings are what
53
+ // matter and those say Chatly.
54
+ this.visitorToken = localStorage.getItem(`livechat:vt:${opts.publicId}`);
55
+ }
56
+ open() {
57
+ window.Chatly?.open?.();
58
+ }
59
+ close() {
60
+ window.Chatly?.close?.();
61
+ }
62
+ /**
63
+ * Attach a known identity to this visitor.
64
+ *
65
+ * Re-initialises the session with an `identity` payload rather than calling
66
+ * a dedicated endpoint. There has never been a `/v1/widget/identify` route —
67
+ * this method has 404'd since it was written, as have the Kotlin and Swift
68
+ * equivalents. Identity is accepted by `init`, which is also where the
69
+ * `userHash` check for verified identity lives, so routing through it means
70
+ * identification and verification cannot drift apart.
71
+ *
72
+ * `identify({ email })` with no `externalId` — the exact call this file's
73
+ * own header comment demonstrates — used to be rejected by the server with
74
+ * a 400, because `widgetInitSchema` required `externalId`. It no longer is;
75
+ * an email-only identity attaches a reply address to whoever this browser
76
+ * already is. It cannot be *verified*, though: `userHash` binds the external
77
+ * id and there is nothing to bind without one.
78
+ *
79
+ * No longer mints an anonymous session first. That cost a round trip, and
80
+ * worse, it was pointless: the token the server returns HERE is the
81
+ * identified one, and it is stored below. Before, the SDK kept the
82
+ * anonymous token it had just fetched, so every later `track` or `send`
83
+ * spoke for the anonymous visitor and the identification was quietly lost.
84
+ */
85
+ async identify(input) {
86
+ const res = await this.request('/v1/widget/init', {
87
+ publicId: this.opts.publicId,
88
+ ...(this.visitorToken === null ? {} : { visitorToken: this.visitorToken }),
89
+ identity: input,
90
+ });
91
+ const body = (await res.json());
92
+ if (typeof body.realtimeUrl === 'string' && body.realtimeUrl.length > 0) {
93
+ this.realtimeUrl = body.realtimeUrl;
94
+ }
95
+ // The session token is now an opaque server-issued credential rather than
96
+ // the external id, and `init` rotates it on every call — so it has to be
97
+ // taken from the response, not assumed to be unchanged.
98
+ const token = body.session?.token;
99
+ if (typeof token === 'string' && token.length > 0) {
100
+ this.setVisitorToken(token);
101
+ }
102
+ const contactId = body.session?.contactId ?? '';
103
+ if (contactId.length > 0) {
104
+ /*
105
+ * Remembered here as well as in `ensureVisitorToken`, because `identify`
106
+ * can be the FIRST call a session makes and `ensureVisitorToken` returns
107
+ * early when a token is already in localStorage. A returning browser
108
+ * therefore never learned its own contact id, and `setTyping` sent
109
+ * `actorId: 'unknown'` — which the realtime gateway now overwrites from
110
+ * the JWT anyway, but a field we can fill correctly should not be a
111
+ * placeholder.
112
+ */
113
+ this.contactId = contactId;
114
+ }
115
+ return { contactId };
116
+ }
117
+ /**
118
+ * Upload a file as this visitor and return the attachment record.
119
+ *
120
+ * The two-step presign + direct PUT is deliberate: the file never passes
121
+ * through the API, which would mean proxying it in memory only to write it
122
+ * to object storage anyway.
123
+ */
124
+ async uploadFile(file) {
125
+ await this.ensureVisitorToken();
126
+ const res = await this.request('/v1/widget/uploads/presign', {
127
+ publicId: this.opts.publicId,
128
+ visitorToken: this.visitorToken,
129
+ filename: file.name,
130
+ contentType: file.type || 'application/octet-stream',
131
+ sizeBytes: file.size,
132
+ });
133
+ const presigned = (await res.json());
134
+ const put = await fetch(presigned.url, {
135
+ method: presigned.method,
136
+ headers: presigned.headers,
137
+ body: file,
138
+ });
139
+ if (!put.ok) {
140
+ throw new Error(`Upload failed (${put.status})`);
141
+ }
142
+ return {
143
+ id: presigned.uploadId,
144
+ filename: file.name,
145
+ mimeType: file.type || 'application/octet-stream',
146
+ sizeBytes: file.size,
147
+ url: presigned.publicUrl,
148
+ };
149
+ }
150
+ /** Send a message, optionally with attachments from `uploadFile`. */
151
+ async send(conversationId, body, attachments = []) {
152
+ await this.ensureVisitorToken();
153
+ await this.request('/v1/widget/messages', {
154
+ publicId: this.opts.publicId,
155
+ visitorToken: this.visitorToken,
156
+ conversationId,
157
+ body,
158
+ attachments,
159
+ });
160
+ }
161
+ // ── Headless: read ────────────────────────────────────────────────────
162
+ //
163
+ // Everything below exists so a consumer can build their OWN chat UI. Before
164
+ // it, the SDK could `send` into a conversation but could not create one,
165
+ // list threads, read history or learn that a reply had arrived — so it was a
166
+ // companion to the widget, not a client you could render from.
167
+ /**
168
+ * This visitor's own conversations, most recent first.
169
+ *
170
+ * Scoped by the visitor token, so it is exactly the threads this browser is
171
+ * entitled to and never anyone else's.
172
+ */
173
+ async listConversations(limit = 20) {
174
+ await this.ensureVisitorToken();
175
+ const res = await this.request('/v1/widget/conversations/list', {
176
+ publicId: this.opts.publicId,
177
+ visitorToken: this.visitorToken,
178
+ limit,
179
+ });
180
+ const body = (await res.json());
181
+ return body.conversations ?? [];
182
+ }
183
+ /** The messages of one conversation, oldest first. */
184
+ async listMessages(conversationId, limit = 50) {
185
+ await this.ensureVisitorToken();
186
+ const res = await this.request('/v1/widget/messages/list', {
187
+ publicId: this.opts.publicId,
188
+ visitorToken: this.visitorToken,
189
+ conversationId,
190
+ limit,
191
+ });
192
+ const body = (await res.json());
193
+ return body.messages ?? [];
194
+ }
195
+ // ── Headless: write ───────────────────────────────────────────────────
196
+ /**
197
+ * Open a NEW conversation with a first message.
198
+ *
199
+ * `send` replies to one that exists and requires a `conversationId`; this is
200
+ * the other endpoint. Getting them the wrong way round is a 400 complaining
201
+ * about a missing `conversationId`, which is how the repo's own E2E spec was
202
+ * written and why it could never pass.
203
+ */
204
+ async startConversation(body, attachments = []) {
205
+ await this.ensureVisitorToken();
206
+ const res = await this.request('/v1/widget/conversations', {
207
+ publicId: this.opts.publicId,
208
+ visitorToken: this.visitorToken,
209
+ body,
210
+ attachments,
211
+ });
212
+ const parsed = (await res.json());
213
+ return { conversationId: parsed.conversation?.id ?? '' };
214
+ }
215
+ // ── Headless: live ────────────────────────────────────────────────────
216
+ /**
217
+ * Call `onMessage` for every message this subscription has not already
218
+ * given you. Returns the unsubscribe function.
219
+ *
220
+ * **Polling by default**, because the alternative is making every consumer
221
+ * ship `socket.io-client` whether they want push or not. Pass your own
222
+ * factory for push:
223
+ *
224
+ * import { io } from 'socket.io-client';
225
+ * const stop = chatly.subscribe(id, { onMessage }, { io });
226
+ *
227
+ * Errors go to `onError`, never a rejected promise: this is long-lived, and
228
+ * a rejection nobody awaits is an unhandled rejection on the customer's own
229
+ * page.
230
+ */
231
+ subscribe(conversationId, handlers, options = {}) {
232
+ const seen = new Set();
233
+ let stopped = false;
234
+ /** Set once the push socket is up; null on the polling path. */
235
+ let live = null;
236
+ let warnedAboutTyping = false;
237
+ /*
238
+ * Receiving typing needs the same transport as sending it, and for the
239
+ * same reason: there is no HTTP route. A consumer who asked for
240
+ * `onTyping` on the polling path has asked for something the transport
241
+ * cannot carry, so say it once at subscribe time — the alternative is an
242
+ * agent typing visibly in the dashboard and nothing happening here, with
243
+ * nothing to search for.
244
+ */
245
+ if (handlers.onTyping !== undefined && options.io === undefined) {
246
+ console.warn('[Chatly] onTyping needs the push path — pass `io` to subscribe(). ' +
247
+ 'Typing is a socket event and has no HTTP route, so a polling ' +
248
+ 'subscription never receives one.');
249
+ }
250
+ const typing = createTypingTracker({
251
+ onChange: (signal) => handlers.onTyping?.(signal),
252
+ });
253
+ const setTyping = (isTyping) => {
254
+ if (live === null) {
255
+ if (!warnedAboutTyping) {
256
+ warnedAboutTyping = true;
257
+ console.warn('[Chatly] setTyping needs the push path — pass `io` to subscribe(). ' +
258
+ 'Typing is a socket event and has no HTTP route.');
259
+ }
260
+ return;
261
+ }
262
+ live.emit(isTyping ? 'typing.start' : 'typing.stop', {
263
+ conversationId,
264
+ actorType: 'contact',
265
+ // The gateway's payload requires a non-empty actorId. `unknown` rather
266
+ // than an empty string, which its schema rejects outright.
267
+ actorId: this.contactId ?? 'unknown',
268
+ });
269
+ };
270
+ const deliver = (batch) => {
271
+ for (const message of newMessagesSince(seen, batch)) {
272
+ handlers.onMessage?.(message);
273
+ }
274
+ };
275
+ const poll = async () => {
276
+ if (stopped) {
277
+ return;
278
+ }
279
+ try {
280
+ deliver(await this.listMessages(conversationId));
281
+ }
282
+ catch (err) {
283
+ handlers.onError?.(err);
284
+ }
285
+ };
286
+ // The first batch is the history, which is also how a consumer gets the
287
+ // existing thread without a separate call.
288
+ void poll();
289
+ if (options.io === undefined) {
290
+ // Floored: a one-second poll on a long conversation is a request every
291
+ // second per open tab, and the endpoint is not a stream.
292
+ const interval = Math.max(2_000, options.intervalMs ?? 6_000);
293
+ const timer = setInterval(() => void poll(), interval);
294
+ return Object.assign(() => {
295
+ stopped = true;
296
+ clearInterval(timer);
297
+ // Nothing can have started the tracker on this path — but a
298
+ // teardown that only cleans up the branch it remembers is how a
299
+ // timer outlives its subscription.
300
+ typing.dispose();
301
+ }, { setTyping });
302
+ }
303
+ let socket = null;
304
+ // Captured before the async gap so the narrowing holds without an
305
+ // assertion: `options` is readonly, but the checker cannot know that
306
+ // across an await.
307
+ const makeSocket = options.io;
308
+ void (async () => {
309
+ try {
310
+ await this.ensureVisitorToken();
311
+ const res = await this.request('/v1/widget/realtime/token', {
312
+ publicId: this.opts.publicId,
313
+ visitorToken: this.visitorToken,
314
+ conversationId,
315
+ });
316
+ const { token } = (await res.json());
317
+ const host = this.opts.realtimeUrl ?? this.realtimeUrl;
318
+ if (stopped || typeof token !== 'string' || host === null || host === undefined) {
319
+ /*
320
+ * No host means no socket. Reported rather than silently falling
321
+ * back, because a consumer who passed `io` asked for push and a
322
+ * quiet downgrade to polling would look like push that is slow.
323
+ */
324
+ handlers.onError?.(new Error('chatly: no realtime host — set `realtimeUrl` in init options'));
325
+ return;
326
+ }
327
+ const made = makeSocket(host, {
328
+ path: '/socket.io',
329
+ transports: ['websocket'],
330
+ auth: { token, kind: 'visitor' },
331
+ });
332
+ socket = made;
333
+ live = made;
334
+ made.on('event', (raw) => {
335
+ const envelope = raw;
336
+ if (envelope?.type === 'message.created' && envelope.payload !== undefined) {
337
+ deliver([envelope.payload]);
338
+ /*
339
+ * A reply has arrived, so whoever was typing has stopped — the
340
+ * dashboard sends `typing.stop` on send, but the message and the
341
+ * stop are two envelopes and the message usually wins the race.
342
+ * Without this the indicator sits under the message that was
343
+ * being typed, for the length of the timeout.
344
+ */
345
+ typing.stop();
346
+ return;
347
+ }
348
+ if (envelope?.type === 'typing.start' || envelope?.type === 'typing.stop') {
349
+ const actor = typingActorOf(envelope.payload);
350
+ if (actor === null) {
351
+ return;
352
+ }
353
+ if (envelope.type === 'typing.start') {
354
+ typing.start(actor);
355
+ }
356
+ else {
357
+ typing.stop(actor);
358
+ }
359
+ }
360
+ });
361
+ }
362
+ catch (err) {
363
+ /*
364
+ * Push could not be set up. Fall back to polling rather than leaving
365
+ * the consumer with a subscription that silently never fires — the
366
+ * same choice the widget makes when its realtime chunk cannot load.
367
+ */
368
+ handlers.onError?.(err);
369
+ if (!stopped) {
370
+ const timer = setInterval(() => void poll(), 6_000);
371
+ socket = { disconnect: () => clearInterval(timer) };
372
+ }
373
+ }
374
+ })();
375
+ return Object.assign(() => {
376
+ stopped = true;
377
+ live = null;
378
+ typing.dispose();
379
+ socket?.disconnect();
380
+ }, { setTyping });
381
+ }
382
+ /** Submit a post-chat satisfaction score (1-5, or 0-10 for NPS). */
383
+ async rate(conversationId, score, comment) {
384
+ await this.ensureVisitorToken();
385
+ await this.request('/v1/widget/csat', {
386
+ publicId: this.opts.publicId,
387
+ visitorToken: this.visitorToken,
388
+ conversationId,
389
+ score,
390
+ ...(comment === undefined ? {} : { comment }),
391
+ });
392
+ }
393
+ /** Email this visitor a transcript of their conversation. */
394
+ async emailTranscript(conversationId, email) {
395
+ await this.ensureVisitorToken();
396
+ await this.request('/v1/widget/transcript', {
397
+ publicId: this.opts.publicId,
398
+ visitorToken: this.visitorToken,
399
+ conversationId,
400
+ email,
401
+ });
402
+ }
403
+ async track(name, properties) {
404
+ await this.ensureVisitorToken();
405
+ await this.request('/v1/widget/events', {
406
+ publicId: this.opts.publicId,
407
+ visitorToken: this.visitorToken,
408
+ name,
409
+ properties: properties ?? {},
410
+ });
411
+ }
412
+ setVisitorToken(token) {
413
+ this.visitorToken = token;
414
+ localStorage.setItem(`livechat:vt:${this.opts.publicId}`, token);
415
+ }
416
+ async ensureVisitorToken() {
417
+ if (this.visitorToken !== null) {
418
+ return;
419
+ }
420
+ const res = await this.request('/v1/widget/init', { publicId: this.opts.publicId });
421
+ const body = (await res.json());
422
+ this.setVisitorToken(body.session.token);
423
+ if (typeof body.session.contactId === 'string' && body.session.contactId.length > 0) {
424
+ this.contactId = body.session.contactId;
425
+ }
426
+ /*
427
+ * The realtime host is reported here as well as by `identify`, and this is
428
+ * the path `subscribe` bootstraps through — an anonymous visitor never
429
+ * calls `identify`. Captured in both, because either can be the first
430
+ * init a session ever makes.
431
+ */
432
+ if (typeof body.realtimeUrl === 'string' && body.realtimeUrl.length > 0) {
433
+ this.realtimeUrl = body.realtimeUrl;
434
+ }
435
+ }
436
+ async request(path, body, isRetry = false) {
437
+ const res = await fetch(`${this.base}${path}`, {
438
+ method: 'POST',
439
+ headers: {
440
+ 'content-type': 'application/json',
441
+ // Production API requires Idempotency-Key on every write; mint
442
+ // a fresh UUID per call so a stray fetch-retry never duplicates.
443
+ 'idempotency-key': newIdempotencyKey(),
444
+ },
445
+ body: JSON.stringify(body),
446
+ });
447
+ if (!res.ok) {
448
+ if (res.status === 403 && !isRetry && (await isStaleSession(res))) {
449
+ // The stored token predates signed visitor sessions, or expired. The
450
+ // integrator's code did nothing wrong and should not have to handle
451
+ // it: drop the dead token, get a fresh one, replay the call once.
452
+ this.visitorToken = null;
453
+ localStorage.removeItem(`livechat:vt:${this.opts.publicId}`);
454
+ await this.ensureVisitorToken();
455
+ const replayed = { ...body };
456
+ if (replayed['visitorToken'] !== undefined) {
457
+ replayed['visitorToken'] = this.visitorToken;
458
+ }
459
+ return this.request(path, replayed, true);
460
+ }
461
+ throw new Error(`Chatly SDK request failed: ${res.status}`);
462
+ }
463
+ return res;
464
+ }
465
+ }
466
+ /**
467
+ * Is this 403 "your session token is no good", rather than one of the other
468
+ * things a widget call can be forbidden for (a disallowed origin, a missing
469
+ * identity hash)? Only the session codes are cured by fetching a new token.
470
+ */
471
+ /**
472
+ * Does this 403 mean "your session token is no good"?
473
+ *
474
+ * This read `body.code` at the top level, and the API has never sent that
475
+ * shape: `app-exception.filter.ts` wraps every error as
476
+ * `{ error: { code, message, details } }`. So it was `undefined` for every
477
+ * real response, this returned false every time, and **the session-recovery
478
+ * path below never fired** — a visitor holding an expired token got failures
479
+ * the SDK was written to absorb.
480
+ *
481
+ * The widget had the identical bug, fixed on 2026-10-08, and nobody checked
482
+ * its sibling. Both shapes are read now: the nested one the API sends, and
483
+ * the flat one in case anything ever answers that way.
484
+ */
485
+ async function isStaleSession(res) {
486
+ try {
487
+ const body = (await res.json());
488
+ const code = body.error?.code ?? body.code;
489
+ return typeof code === 'string' && code.startsWith('visitor_session_');
490
+ }
491
+ catch {
492
+ return false;
493
+ }
494
+ }
495
+ function newIdempotencyKey() {
496
+ // crypto.randomUUID is in every browser the widget supports
497
+ // (Chrome 92+, Safari 15.4+, Firefox 95+).
498
+ return globalThis.crypto.randomUUID();
499
+ }
500
+ export default Chatly;
501
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/live.d.ts ADDED
@@ -0,0 +1,99 @@
1
+ /**
2
+ * The live half of the headless SDK.
3
+ *
4
+ * A consumer building their own chat UI needs messages as they arrive. Two
5
+ * ways, and the SDK does not require the heavier one:
6
+ *
7
+ * - **Polling**, the default. Zero dependencies, works everywhere, a few
8
+ * seconds of latency.
9
+ * - **Push**, by handing `subscribe()` your own socket.io-client factory.
10
+ * The widget does the same thing with a deferred chunk; the SDK will not
11
+ * take the dependency on a consumer's behalf, because a React Native app
12
+ * or a worker may have no use for it and every byte would still ship.
13
+ *
14
+ * This file holds the part worth testing on its own: deciding what is new.
15
+ */
16
+ import type { HeadlessMessage, TypingSignal } from './types.js';
17
+ /**
18
+ * The messages in `batch` that `seen` has not yet been told about, oldest
19
+ * first, with `seen` updated.
20
+ *
21
+ * Ordered by `createdAt` rather than by the order the server returned: a list
22
+ * endpoint is free to answer newest-first, and a consumer appending in
23
+ * receive order would render the conversation upside down.
24
+ *
25
+ * Deliberately NOT the widget's `mergePolled`. That one reconciles optimistic
26
+ * `local-*` rows against their server copies, which only matters to a UI that
27
+ * renders a message before the server has one. A headless consumer owns that
28
+ * decision themselves; all they need from us is "what is new".
29
+ */
30
+ export declare function newMessagesSince(seen: Set<string>, batch: readonly HeadlessMessage[]): HeadlessMessage[];
31
+ /**
32
+ * How long a "typing" indicator survives without another `typing.start`.
33
+ *
34
+ * ── Why the SDK owns this timeout and not the consumer ──────────────────
35
+ *
36
+ * `typing.stop` is best-effort. The dashboard emits one after 5s of idle
37
+ * keystrokes and on send, and its hook emits one on unmount — but an agent
38
+ * who closes the laptop mid-sentence, loses their network, or whose tab is
39
+ * killed sends nothing at all. A consumer rendering the raw start/stop pair
40
+ * gets an indicator that says "Sarah is typing…" forever, which is the exact
41
+ * shape of bug this codebase keeps finding: it appears to work.
42
+ *
43
+ * Leaving it to the consumer means every consumer writes this same timer, and
44
+ * the ones who do not ship the stuck indicator. 3,500ms is the widget's own
45
+ * window (`use-conversation-realtime.tsx`), so a custom UI built on the SDK
46
+ * behaves like the widget rather than subtly differently.
47
+ */
48
+ export declare const TYPING_STOP_AFTER_MS = 3500;
49
+ /** What the tracker needs from a `typing.*` payload. */
50
+ interface TypingActor {
51
+ readonly actorType: 'agent' | 'bot';
52
+ readonly actorId: string;
53
+ readonly displayName?: string;
54
+ }
55
+ /**
56
+ * Turn a stream of `typing.start` / `typing.stop` events into the single
57
+ * boolean a UI actually renders.
58
+ *
59
+ * Edge-triggered: `onChange` fires only when the answer changes, so a
60
+ * keystroke every 300ms does not re-render the consumer's component ten times
61
+ * a second. Exported for its own test — the timer behaviour is the part worth
62
+ * pinning, and it is invisible from the outside.
63
+ */
64
+ export declare function createTypingTracker(opts: {
65
+ readonly onChange: (signal: TypingSignal) => void;
66
+ readonly stopAfterMs?: number;
67
+ }): {
68
+ start: (actor: TypingActor) => void;
69
+ /**
70
+ * `actor` is optional: the caller does not always have one. A message
71
+ * arriving means "whoever was typing has stopped", and the envelope that
72
+ * carries it says nothing about typing at all.
73
+ */
74
+ stop: (actor?: TypingActor) => void;
75
+ dispose: () => void;
76
+ };
77
+ /**
78
+ * The actor a `typing.*` envelope is about, or null if it is not one we show.
79
+ *
80
+ * Two exclusions, both load-bearing:
81
+ *
82
+ * - `kind: 'viewing'`. The agent dashboard publishes room presence and
83
+ * collaborative-cursor frames AS `typing.start` (see `viewing.cursor` in
84
+ * `apps/realtime/src/gateway.ts`), ~10 per second while an agent moves
85
+ * their mouse. Rendering those as typing would make the indicator flicker
86
+ * whenever an agent so much as looked at the conversation.
87
+ * - anything that is not an agent or a bot. The gateway's visitor allowlist
88
+ * already forwards only agent typing, so this is the second of two checks
89
+ * rather than the only one — but a visitor must never be shown their OWN
90
+ * typing as the agent's, and the day the allowlist widens is not the day
91
+ * to discover that.
92
+ */
93
+ export declare function typingActorOf(payload: unknown): {
94
+ actorType: 'agent' | 'bot';
95
+ actorId: string;
96
+ displayName?: string;
97
+ } | null;
98
+ export {};
99
+ //# sourceMappingURL=live.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live.d.ts","sourceRoot":"","sources":["../src/live.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAYhE;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,EACjB,KAAK,EAAE,SAAS,eAAe,EAAE,GAChC,eAAe,EAAE,CA4BnB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,oBAAoB,OAAQ,CAAC;AAE1C,wDAAwD;AACxD,UAAU,WAAW;IACnB,QAAQ,CAAC,SAAS,EAAE,OAAO,GAAG,KAAK,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IACxC,QAAQ,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,CAAC;IAClD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B,GAAG;IACF,KAAK,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IACpC;;;;OAIG;IACH,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE,WAAW,KAAK,IAAI,CAAC;IACpC,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAgDA;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,OAAO,GACf;IAAE,SAAS,EAAE,OAAO,GAAG,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAqB9E"}