@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/CHANGELOG.md ADDED
@@ -0,0 +1,21 @@
1
+ # @chatlychat/sdk-js — changelog
2
+
3
+ ## Unreleased
4
+
5
+ - `subscribe` now reports INBOUND typing through `handlers.onTyping` — the
6
+ send half (`setTyping`) shipped first and there was no way to show the
7
+ visitor that the agent was typing. Push-only, like `setTyping`: typing is a
8
+ socket event with no HTTP route, so a polling subscription warns once rather
9
+ than never firing in silence.
10
+ - `onTyping` is edge-triggered and self-clearing (`TYPING_STOP_AFTER_MS`,
11
+ 3.5s — the widget's own window), because `typing.stop` is best-effort and an
12
+ agent who closes their laptop mid-sentence never sends one.
13
+ - `identify()` now remembers the contact id it was given, so `setTyping` names
14
+ the right contact on a returning browser instead of `'unknown'`.
15
+
16
+ ## 0.1.0 — 2026-05-25
17
+
18
+ - Initial public release.
19
+ - `Chatly` class: `identify`, `track`, `open`, `close`, `toggle`, `send`, `on`,
20
+ `destroy`.
21
+ - TypeScript types ship with the package; no `@types/*` package needed.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Chatly Engineering
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,82 @@
1
- # Temporary Holding Version
1
+ # @chatlychat/sdk-js
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Browser SDK for the Chatly platform: control the in-page widget, identify
4
+ visitors, fire CDP events, and — headlessly — list conversations, send
5
+ messages and subscribe to replies without rendering our UI at all.
6
+
7
+ > **Not published to npm yet.** The install line below will 404 today. The
8
+ > widget-control half of this surface is already available with no package:
9
+ > load the widget snippet and call the methods on `window.Chatly`. The
10
+ > headless half (`listConversations`, `listMessages`, `send`, `uploadFile`,
11
+ > `subscribe`, `rate`, `emailTranscript`) has **no** global and needs this
12
+ > package or a direct call to the documented REST endpoints.
13
+
14
+ ```bash
15
+ npm install @chatlychat/sdk-js
16
+ # or
17
+ pnpm add @chatlychat/sdk-js
18
+ ```
19
+
20
+ ## Quick start
21
+
22
+ ```ts
23
+ import { Chatly } from '@chatlychat/sdk-js';
24
+
25
+ // The constructor is PRIVATE. `init` is the only way in.
26
+ const chatly = Chatly.init({ publicId: 'ch_web_0192…' });
27
+
28
+ await chatly.identify({ externalId: 'user_42', name: 'Ada', userHash: '…' });
29
+ const { conversationId } = await chatly.startConversation('Hello');
30
+ await chatly.send(conversationId, 'and one more thing');
31
+ ```
32
+
33
+ ## `Chatly.init(options)`
34
+
35
+ | Option | Type | Default | Notes |
36
+ | ------------- | --------- | ---------------------------- | ------------------------------------------------------------------------- |
37
+ | `publicId` | `string` | — | The web channel's public id, from Dashboard → Channels. |
38
+ | `apiUrl` | `string?` | `https://api.chatlychat.com` | Set only if you self-host. |
39
+ | `realtimeUrl` | `string?` | reported by the server | Socket.IO host for `subscribe()`. Only for a host the server cannot know. |
40
+
41
+ ## Methods
42
+
43
+ Widget control — these mirror `window.Chatly` and need the widget on the page:
44
+
45
+ - `open()` / `close()`
46
+
47
+ Visitor identity and analytics:
48
+
49
+ - `identify(input)` → `{ contactId }`. Pass `userHash` to make the identity
50
+ **verified**; without it the visitor is unverified and tools that require a
51
+ proven identity will refuse.
52
+ - `track(name, properties?)`
53
+
54
+ Headless conversation surface — no widget required:
55
+
56
+ - `startConversation(body, attachments?)` → `{ conversationId }`
57
+ - `send(conversationId, body, attachments?)`
58
+ - `listConversations(limit = 20)`
59
+ - `listMessages(conversationId, limit = 50)`
60
+ - `subscribe(conversationId, handlers, options?)` → `Subscription`. Uses the
61
+ push socket when it can and falls back to polling, so it keeps working
62
+ where a WebSocket cannot be opened.
63
+ - `uploadFile(file)` → `VisitorAttachment`, to pass to `send` or
64
+ `startConversation`
65
+ - `rate(conversationId, score, comment?)`
66
+ - `emailTranscript(conversationId, email)`
67
+
68
+ ## Signed page context
69
+
70
+ `setContext` is **not** on this SDK — it is on the widget's own global. See
71
+ the [web channel guide](https://chatlychat.com/guides/channel-web) for the
72
+ call and [identity verification](https://chatlychat.com/docs/identity-verification)
73
+ for how to mint the signed `contextToken`.
74
+
75
+ ## This README used to document an API that did not exist
76
+
77
+ For the record, because it may have been copied: it described
78
+ `new Chatly({ channelPublicId, baseUrl, widgetSelector, locale })`, plus
79
+ `toggle()`, `on()`, `destroy()` and `send(body)`. **None of those exist.** The
80
+ constructor is private, the option names are `publicId` / `apiUrl` /
81
+ `realtimeUrl`, and `send` takes a conversation id first. Corrected
82
+ 2026-10-12 against `src/index.ts`.
@@ -0,0 +1,175 @@
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 type { HeadlessConversation, HeadlessMessage, SubscribeHandlers, SubscribeOptions, Subscription } from './types.js';
12
+ export type { HeadlessConversation, HeadlessMessage, SubscribeHandlers, SubscribeOptions, SocketIoFactory, Subscription, TypingSignal, } from './types.js';
13
+ export { TYPING_STOP_AFTER_MS } from './live.js';
14
+ export interface SdkOptions {
15
+ publicId: string;
16
+ apiUrl?: string;
17
+ /**
18
+ * Socket.IO host, for the push path of `subscribe()`.
19
+ *
20
+ * Normally unnecessary: `/v1/widget/init` reports the right one and the SDK
21
+ * remembers it. Set this only for a deployment whose realtime host the
22
+ * server cannot know — the same escape hatch the widget's embed has.
23
+ */
24
+ realtimeUrl?: string;
25
+ }
26
+ export interface IdentifyInput {
27
+ /**
28
+ * Your own id for this person. Optional — see `identify` — but it is the
29
+ * only field identity verification can bind, so a channel with verification
30
+ * enabled needs this plus `userHash` to consider the visitor proven.
31
+ */
32
+ externalId?: string;
33
+ email?: string;
34
+ phone?: string;
35
+ name?: string;
36
+ /**
37
+ * `HMAC-SHA256(<your channel's IDV secret>, externalId)`, computed by YOUR
38
+ * SERVER. Never compute it in the browser — the secret would be in your
39
+ * page source. Required when the channel has identity verification enabled
40
+ * and you are claiming an `externalId`; ignored when it does not.
41
+ */
42
+ userHash?: string;
43
+ attributes?: Record<string, unknown>;
44
+ }
45
+ export interface TrackInput {
46
+ name: string;
47
+ properties?: Record<string, unknown>;
48
+ }
49
+ declare global {
50
+ interface Window {
51
+ Chatly?: {
52
+ open?: () => void;
53
+ close?: () => void;
54
+ destroy?: () => void;
55
+ };
56
+ }
57
+ }
58
+ /**
59
+ * An uploaded file, as returned by `uploadFile` and accepted by `send`.
60
+ *
61
+ * Declared locally rather than imported from `@livechat/shared`: this bundle
62
+ * ships to a customer's website, and importing from the shared package drags
63
+ * zod into it — measured at ~11KB gzipped when it happened to the widget.
64
+ */
65
+ export interface VisitorAttachment {
66
+ readonly id: string;
67
+ readonly filename: string;
68
+ readonly mimeType: string;
69
+ readonly sizeBytes: number;
70
+ readonly url: string;
71
+ }
72
+ export declare class Chatly {
73
+ private readonly opts;
74
+ private readonly base;
75
+ private visitorToken;
76
+ /**
77
+ * The realtime host `/v1/widget/init` reported, remembered for `subscribe`.
78
+ *
79
+ * `/v1/widget/realtime/token` returns `{ token, expiresAt }` and NO url —
80
+ * the first version of `subscribe` read a `url` off it and silently fell
81
+ * back to the API host, which is not where the socket server is.
82
+ */
83
+ private realtimeUrl;
84
+ /**
85
+ * This visitor's contact id, remembered for the typing event — the realtime
86
+ * gateway's payload requires an `actorId` and the visitor's is their contact.
87
+ */
88
+ private contactId;
89
+ static init(opts: SdkOptions): Chatly;
90
+ private constructor();
91
+ open(): void;
92
+ close(): void;
93
+ /**
94
+ * Attach a known identity to this visitor.
95
+ *
96
+ * Re-initialises the session with an `identity` payload rather than calling
97
+ * a dedicated endpoint. There has never been a `/v1/widget/identify` route —
98
+ * this method has 404'd since it was written, as have the Kotlin and Swift
99
+ * equivalents. Identity is accepted by `init`, which is also where the
100
+ * `userHash` check for verified identity lives, so routing through it means
101
+ * identification and verification cannot drift apart.
102
+ *
103
+ * `identify({ email })` with no `externalId` — the exact call this file's
104
+ * own header comment demonstrates — used to be rejected by the server with
105
+ * a 400, because `widgetInitSchema` required `externalId`. It no longer is;
106
+ * an email-only identity attaches a reply address to whoever this browser
107
+ * already is. It cannot be *verified*, though: `userHash` binds the external
108
+ * id and there is nothing to bind without one.
109
+ *
110
+ * No longer mints an anonymous session first. That cost a round trip, and
111
+ * worse, it was pointless: the token the server returns HERE is the
112
+ * identified one, and it is stored below. Before, the SDK kept the
113
+ * anonymous token it had just fetched, so every later `track` or `send`
114
+ * spoke for the anonymous visitor and the identification was quietly lost.
115
+ */
116
+ identify(input: IdentifyInput): Promise<{
117
+ contactId: string;
118
+ }>;
119
+ /**
120
+ * Upload a file as this visitor and return the attachment record.
121
+ *
122
+ * The two-step presign + direct PUT is deliberate: the file never passes
123
+ * through the API, which would mean proxying it in memory only to write it
124
+ * to object storage anyway.
125
+ */
126
+ uploadFile(file: File): Promise<VisitorAttachment>;
127
+ /** Send a message, optionally with attachments from `uploadFile`. */
128
+ send(conversationId: string, body: string, attachments?: VisitorAttachment[]): Promise<void>;
129
+ /**
130
+ * This visitor's own conversations, most recent first.
131
+ *
132
+ * Scoped by the visitor token, so it is exactly the threads this browser is
133
+ * entitled to and never anyone else's.
134
+ */
135
+ listConversations(limit?: number): Promise<HeadlessConversation[]>;
136
+ /** The messages of one conversation, oldest first. */
137
+ listMessages(conversationId: string, limit?: number): Promise<HeadlessMessage[]>;
138
+ /**
139
+ * Open a NEW conversation with a first message.
140
+ *
141
+ * `send` replies to one that exists and requires a `conversationId`; this is
142
+ * the other endpoint. Getting them the wrong way round is a 400 complaining
143
+ * about a missing `conversationId`, which is how the repo's own E2E spec was
144
+ * written and why it could never pass.
145
+ */
146
+ startConversation(body: string, attachments?: VisitorAttachment[]): Promise<{
147
+ conversationId: string;
148
+ }>;
149
+ /**
150
+ * Call `onMessage` for every message this subscription has not already
151
+ * given you. Returns the unsubscribe function.
152
+ *
153
+ * **Polling by default**, because the alternative is making every consumer
154
+ * ship `socket.io-client` whether they want push or not. Pass your own
155
+ * factory for push:
156
+ *
157
+ * import { io } from 'socket.io-client';
158
+ * const stop = chatly.subscribe(id, { onMessage }, { io });
159
+ *
160
+ * Errors go to `onError`, never a rejected promise: this is long-lived, and
161
+ * a rejection nobody awaits is an unhandled rejection on the customer's own
162
+ * page.
163
+ */
164
+ subscribe(conversationId: string, handlers: SubscribeHandlers, options?: SubscribeOptions): Subscription;
165
+ /** Submit a post-chat satisfaction score (1-5, or 0-10 for NPS). */
166
+ rate(conversationId: string, score: number, comment?: string): Promise<void>;
167
+ /** Email this visitor a transcript of their conversation. */
168
+ emailTranscript(conversationId: string, email: string): Promise<void>;
169
+ track(name: string, properties?: Record<string, unknown>): Promise<void>;
170
+ private setVisitorToken;
171
+ private ensureVisitorToken;
172
+ private request;
173
+ }
174
+ export default Chatly;
175
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}