@paramms/chat-widget 1.0.40 → 1.0.42

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
@@ -8,7 +8,9 @@ Real-time embeddable chat widget for the Relay platform. Drop into any website w
8
8
  npm install @paramms/chat-widget
9
9
  ```
10
10
 
11
- ## Quick start — CDN (no npm, no build)
11
+ ## Quick start — CDN, ESM `mount()` (no npm, no build, manual container)
12
+
13
+ This is the ESM path — you control the mount point yourself. For a floating bubble on any site with zero JavaScript (or a config object), see [Script tag](#script-tag-any-website--plain-html-wordpress-shopify-phprailsdjangolaravel-or-literally-anything) below instead — it's usually simpler unless you specifically need to place the widget inline in your own container (`data-relay-*`/`Relay('boot', ...)` don't support choosing a container element; this ESM form does, via `el`).
12
14
 
13
15
  ```html
14
16
  <div id="chat"></div>
@@ -111,9 +113,157 @@ Prefer a floating bubble that opens the same app in a panel? `<ChatAppLauncher
111
113
 
112
114
  > **Multi-tenancy note:** conversations are created and keyed under the tenant the server resolves *from* `profileId` — guests can't spoof it. For **reading your own inbox**, `ChatApp`/`ChatAppLauncher` (and `useRelayChatList`/`mountChatList`/`GET /conversations/mine`) also accept a **`tenantId`** directly: it lists every conversation the user has with that business across all of its chatrooms, and is equally safe because the list is always self-scoped to the caller's own identity. `scope="tenant"` / `tenantId` never crosses into another business's data.
113
115
 
116
+ ## Script tag (any website — plain HTML, WordPress, Shopify, PHP/Rails/Django/Laravel, or literally anything)
117
+
118
+ This is the path for every site that isn't React: one `<script>` tag, no build step, no framework. It works two ways — pick based on how much JavaScript you're willing to write:
119
+
120
+ - **Zero-JavaScript** — `data-relay-*` attributes right on the `<script>` tag. This is the one that matters most for a plain-HTML site, a WordPress block, or a no-code builder, because it's the *only* option when there's no JavaScript on the page at all — a server template just fills in attribute values.
121
+ - **Full config** — a JS object (`window.relaySettings = {...}` or `Relay('boot', {...})`, same shape either way). Needed the moment you want something an HTML attribute can't hold: a nested object, an array, a function, or changing the widget's identity *after* the page has loaded (e.g. once a visitor logs in).
122
+
123
+ Both configure the exact same widget with the exact same field names as the React components above — `profileId`, `userName`/`userEmail`/`userAvatar`, `contextTitle`/`contextSubtitle`/`contextStatus` (or `listingTitle`/`listingMeta`/`listingPrice`/`listingStatus` for a marketplace-style card), `accent`, `launcher`, `position`, `launcherMessage`. If you already know the React props, you already know these.
124
+
125
+ ### Zero-JavaScript — `data-relay-*` attributes
126
+
127
+ ```html
128
+ <script
129
+ async
130
+ src="https://relay.paramms.com/embed.js"
131
+ data-relay-app="YOUR_PROFILE_ID"
132
+ data-relay-user="user_123"
133
+ data-relay-user-name="Jane Doe"
134
+ data-relay-user-email="jane@example.com"
135
+ data-relay-accent="#4F63F5"
136
+ data-relay-position="bottom-right"
137
+ ></script>
138
+ ```
139
+
140
+ Drop that before `</body>` and you have a working floating bubble — no other JavaScript needed anywhere on the page. Every attribute is optional except `data-relay-app`.
141
+
142
+ | Attribute | Matches field | Notes |
143
+ |---|---|---|
144
+ | `data-relay-app` | `profileId` | **Required.** `data-relay-profile-id` works identically — same thing, matches the React prop name |
145
+ | `data-relay-user` | `userId` | Unauthenticated stable id. Omit for an anonymous guest |
146
+ | `data-relay-token` | `token` | Signed ES256 JWT — the production identity tier |
147
+ | `data-relay-user-name` | `userName` | Shown to agents |
148
+ | `data-relay-user-email` | `userEmail` | Shown to agents; also powers the offline email fallback |
149
+ | `data-relay-user-avatar` | `userAvatar` | Shown to agents |
150
+ | `data-relay-listing` | `listingId` | Sugar for `subjectId: "listing_<id>"` |
151
+ | `data-relay-context-title` / `-subtitle` / `-status` | `contextTitle` / `contextSubtitle` / `contextStatus` | Context card (general — an order, ticket, booking) |
152
+ | `data-relay-listing-title` / `-meta` / `-price` / `-status` | `listingTitle` / `listingMeta` / `listingPrice` / `listingStatus` | Marketplace card (a specific item — price + status badge). Use these OR the `context-*` set, both build the same card |
153
+ | `data-relay-accent` | `accent` | Brand colour hex |
154
+ | `data-relay-url` | `url` | Only needed for self-hosted Relay |
155
+ | `data-relay-position` | `position` | `bottom-right` \| `bottom-left` |
156
+ | `data-relay-launcher` | `launcher` | Set to `"false"` for an inline (non-floating) widget |
157
+ | `data-relay-launcher-message` / `data-relay-launcher-subtitle` | `launcherMessage` | Two flat attributes combine into the teaser card — see note below |
158
+ | `data-relay-target` | `el` | CSS selector for an existing element to mount into. Ignored in launcher mode |
159
+ | `data-relay-height` | `height` | Inline container height. Ignored in launcher mode |
160
+ | `data-relay-inbox` | `inbox` | `"true"` adds a back-chevron → full conversation list. Requires `data-relay-launcher="false"` — see the note below the table |
161
+ | `data-relay-inbox-start` | `inboxStart` | `"true"` opens **directly on the conversation list** instead of a single thread — for a dedicated "Messages" page. Implies `inbox`. Requires `data-relay-launcher="false"` |
162
+ | `data-relay-inbox-scope` | `inboxScope` | `tenant` (default) \| `profile` |
163
+ | `data-relay-tenant-id` | `tenantId` | Your business id. Lists threads across **all** your chatrooms without naming one — use instead of `data-relay-app` on an inbox page |
164
+
165
+ **What's NOT available as an attribute:** `user`/`subject` as nested objects, `quickReplies`, `i18n`, and `refreshToken` — an HTML attribute can only hold a string, so these need the JS-object form below. (`launcherMessage`'s two attributes are a workaround for exactly this: the React prop takes one `{ title, subtitle }` object, which an attribute can't express, so it's split into two flat attributes that get recombined.)
166
+
167
+ **`inbox` requires `data-relay-launcher="false"`** — same limit React has (a launcher+inbox combination needs a different component there, `ChatAppLauncher`); with the default floating launcher, `data-relay-inbox` is silently ignored.
168
+
169
+ **A whole "Messages" page, zero JavaScript.** `data-relay-inbox` alone only puts the list *behind* a back-chevron — the widget still lands on one thread, which is right for a product page and wrong for a page whose entire job is the inbox. `data-relay-inbox-start` makes the list the landing view, and `data-relay-tenant-id` spans every chatroom you run:
170
+
171
+ ```html
172
+ <div id="relay-inbox" style="height:600px"></div>
173
+
174
+ <script
175
+ async
176
+ src="https://relay.paramms.com/embed.js"
177
+ data-relay-tenant-id="acme"
178
+ data-relay-inbox-start="true"
179
+ data-relay-launcher="false"
180
+ data-relay-target="#relay-inbox"
181
+ data-relay-user="user_123"
182
+ ></script>
183
+ ```
184
+
185
+ Tapping a row opens that conversation against **its own** chatroom; its back-chevron returns to the list. There's no ✕ in this mode — the list is the root view, so there'd be nothing behind it to close to. ✎ compose opens against your default chatroom (the server reports which). This is the script-tag equivalent of React's `<ChatApp tenantId={...} />`.
186
+
187
+ ### Full config — JS object
188
+
189
+ Same field names, all in one place, same object whether it's set on page load or reacts to something happening later:
190
+
191
+ ```html
192
+ <script async src="https://relay.paramms.com/embed.js"></script>
193
+ <script>
194
+ window.relaySettings = {
195
+ profileId: 'YOUR_PROFILE_ID', // required — your chatroom id
196
+ url: undefined, // optional — only for self-hosted Relay
197
+ apiUrl: undefined, // optional — only if REST lives on a different origin than the socket
198
+
199
+ token: undefined, // optional — signed ES256 JWT, production identity (wins over userId)
200
+ userId: 'user_123', // optional — unauthenticated stable id; omit both → anonymous guest
201
+ refreshToken: () => fetchNewToken(), // optional — called when a signed token expires
202
+
203
+ userName: 'Jane Doe', // optional — shown to agents, not identity
204
+ userEmail: 'jane@example.com', // optional — also powers the offline email fallback
205
+ userAvatar: 'https://…/jane.png', // optional
206
+ // or, if it's easier to build one object: user: { name, email, avatar }
207
+
208
+ subjectId: undefined, // optional — pins a dedicated thread; usually just use listingId below
209
+ listingId: '4821', // optional — sugar for subjectId: "listing_4821"
210
+ contextTitle: 'Order #4821', // optional — context card title (general use)
211
+ contextSubtitle: 'Placed Mar 3 · $129.00', // optional — context card subtitle
212
+ contextStatus: 'Shipped', // optional — status badge
213
+ // or, for a marketplace-style card: listingTitle / listingMeta / listingPrice / listingStatus
214
+ // or build the card yourself: subject: { title, subtitle, tags, status }
215
+
216
+ accent: '#4F63F5', // optional — brand colour
217
+ launcher: true, // optional — floating bubble vs inline; default true
218
+ position: 'bottom-right', // optional — 'bottom-right' | 'bottom-left'
219
+ launcherMessage: { title: 'Questions? Chat with us', subtitle: 'Start a conversation' }, // optional — a bare string also works (title only)
220
+
221
+ quickReplies: ['Track my order', 'Return an item'], // optional — reply chips above the input
222
+ i18n: { send: 'Enviar' }, // optional — UI string overrides
223
+ translateLang: undefined, // optional — auto-translate incoming messages (ISO code)
224
+
225
+ // Inline placement (ignored in launcher mode — a floating bubble is a
226
+ // fixed popup mount() owns, not something placed at a point in the page):
227
+ el: '#chat', // optional — CSS selector or element; omit → an auto-created host
228
+ height: '600px', // optional — inline container height
229
+ inbox: false, // optional — back-chevron → full conversation list (inline only, see below)
230
+ inboxScope: 'tenant', // optional — 'tenant' (default, all your chatrooms) | 'profile' (this one only)
231
+ }
232
+ </script>
233
+ ```
234
+
235
+ `Relay('boot', {...})` (below) takes this exact same object — use whichever form fits how the page is built.
236
+
237
+ **`inbox` matches React exactly, including its one limit:** it only works with `launcher: false`. A floating launcher bubble is a fixed-size popup the widget owns; React itself needs a *different* component (`ChatAppLauncher`) for a launcher-with-inbox experience, and the script-tag path draws the same line. With `inbox: true` and `launcher: false`, the widget's header shows a back-chevron; tapping it swaps to the full conversation list (reusing the same list engine the dashboard's own inbox runs on); tapping a row opens that conversation; a ✕ in the list view returns to this widget's original thread.
238
+
239
+ ### Commands — the same object, applied later
240
+
241
+ For identity that arrives after the page loads (a visitor logs in), or SPA-style navigation between pages without a full reload, call `Relay(...)` directly instead of (or in addition to) setting `window.relaySettings`. Same field names as above — `Relay('boot'/'identify'/'update', {...})` all take a `RelaySettings` object, just at a different moment:
242
+
243
+ ```html
244
+ <script async src="https://relay.paramms.com/embed.js"></script>
245
+ <script>
246
+ Relay('boot', { profileId: 'YOUR_PROFILE_ID' }) // mount immediately, anonymous
247
+
248
+ // later, once the visitor logs in:
249
+ Relay('identify', { userId: currentUser.id, userName: currentUser.name }) // merges their guest history in
250
+
251
+ // on navigation to a different listing/order page:
252
+ Relay('update', { listingId: newListing.id, listingTitle: newListing.title })
253
+
254
+ // on logout:
255
+ Relay('shutdown') // removes the widget + local session
256
+ </script>
257
+ ```
258
+
259
+ `Relay(...)` is queue-safe — calls made before `embed.js` finishes loading are never lost. `identify`/`update` merge onto whatever's currently running (a fresh `boot` replaces it entirely).
260
+
261
+ **Network requirement:** if the host page sets a Content-Security-Policy, it must allow the relay connection — `connect-src https://api.paramms.com wss://api.paramms.com;` (both are separate origins to the browser; include both), or your self-hosted equivalent.
262
+
114
263
  ### Old section below (kept for reference)
115
264
 
116
265
  ```tsx
266
+
117
267
  'use client'
118
268
  import { ChatWidget } from '@paramms/chat-widget/react'
119
269
 
@@ -214,16 +364,7 @@ arrives automatically over the socket. Pass `launcherMessage` only to override
214
364
  the dashboard value for one embed (it also renders instantly, before the
215
365
  socket connects).
216
366
 
217
- Script-tag embeds use data attributes:
218
-
219
- ```html
220
- <script
221
- src="https://relay.paramms.com/embed.js"
222
- data-relay-app="YOUR_PROFILE_ID"
223
- data-relay-launcher-message="Questions? Chat with us"
224
- data-relay-launcher-subtitle="Start a conversation"
225
- ></script>
226
- ```
367
+ Script-tag embeds use `data-relay-launcher-message` / `data-relay-launcher-subtitle` (or the `launcherMessage: {title,subtitle}` object in JS-object form) — see the [Script tag](#script-tag-any-website--plain-html-wordpress-shopify-phprailsdjangolaravel-or-literally-anything) section above for the full reference.
227
368
 
228
369
  ## Internationalisation
229
370
 
@@ -234,12 +375,18 @@ Script-tag embeds use data attributes:
234
375
  i18n={{
235
376
  placeholder: 'Écrivez un message…',
236
377
  send: 'Envoyer',
237
- offline: 'Nous sommes hors ligne pour l\'instant',
378
+ offline: 'Nous sommes absents pour le moment', // the away notice
238
379
  poweredBy: '', // empty string hides the footer
239
380
  }}
240
381
  />
241
382
  ```
242
383
 
384
+ `offline` is the fallback text for the **away notice** shown above the composer
385
+ outside the chatroom's office hours. It never blocks anything: guests can always
386
+ send, the message is delivered like any other, and an agent replies when they're
387
+ back. The chatroom's own "offline message" (dashboard → *Widget message &
388
+ availability*) takes precedence over this string.
389
+
243
390
  RTL is detected automatically for Arabic, Hebrew, Persian and Urdu browsers.
244
391
 
245
392
  ## All options
@@ -262,6 +409,8 @@ RTL is detected automatically for Arabic, Hebrew, Persian and Urdu browsers.
262
409
  | `i18n` | `I18nStrings` | English | UI string overrides |
263
410
  | `inbox` | `boolean` | `false` | Add a ‹ back in the chatroom header that opens the full `ChatApp` conversation list (inline widgets only) |
264
411
  | `inboxScope` | `'tenant' \| 'profile'` | `'tenant'` | With `inbox`: list threads across all chatrooms (`tenant`) or just this one (`profile`) |
412
+ | `inboxStart` | `boolean` | `false` | Land ON the conversation list instead of a single thread (a dedicated "Messages" page). Implies `inbox`; requires `launcher: false`. Script-tag/embed only — in React use `<ChatApp/>` directly |
413
+ | `tenantId` | `string` | — | Script-tag/embed only. Your business id: lists threads across ALL your chatrooms with no chatroom named. Only meaningful with `inboxStart` — a single thread still needs a `profileId` |
265
414
 
266
415
  ## Development
267
416
 
@@ -43,11 +43,24 @@ export declare class ConnectionManager {
43
43
  * The outbox is bounded so a prolonged outage can't grow memory without limit
44
44
  * — oldest queued frames are dropped past the cap. */
45
45
  send(frame: ClientFrame): void;
46
+ /** How many frames are waiting to go out. Useful for a host that wants to
47
+ * show "message pending" state, and for asserting the outbox stays bounded. */
48
+ pendingCount(): number;
46
49
  close(): void;
47
50
  private handle;
48
51
  private refreshing;
49
52
  private fatal;
53
+ /** Release queued frames. When called from 'opened' we know the canonical
54
+ * conversation id the server just resolved us to, and queued `send` frames
55
+ * are retargeted to it. A manager only ever opens ONE conversation (its
56
+ * `opts.open`), so every queued send belongs to that thread by
57
+ * construction — but the id it was queued with can be STALE (queued against
58
+ * the previous session's conversation before a reconnect). Retargeting is a
59
+ * no-op in the normal case and rescues the message in the stale one. */
50
60
  private flush;
61
+ /** Bounded enqueue — the cap lives here so EVERY path that queues respects
62
+ * it (a failed `raw` used to push straight onto the array, bypassing it). */
63
+ private queue;
51
64
  private raw;
52
65
  private onClosed;
53
66
  }
package/dist/embed.d.ts CHANGED
@@ -1,9 +1,19 @@
1
- /** Everything an embedder can pass. All optional except `appId`. Names are
2
- * intentionally plain no knowledge of the widget internals required. */
1
+ import { type MountOptions, type UserInfo } from './index.js';
2
+ /** Everything an embedder can pass. All optional except `profileId`. Field
3
+ * names DELIBERATELY mirror the React props (`ChatWidgetProps` /
4
+ * `MarketplaceChatProps` in react.tsx) so the same mental model — and often
5
+ * the same field names — carries over whether you're using React or a plain
6
+ * script tag. Where a name changed over time the old one still works (see
7
+ * the `@deprecated` notes) — this is a published package embedded on live
8
+ * customer sites (WordPress plugin, Shopify theme block), so nothing here is
9
+ * ever removed, only added to. */
3
10
  export interface RelaySettings {
4
- /** The chatroom id (from your Relay dashboard). Required. `profileId` is an alias. */
5
- appId?: string;
11
+ /** The chatroom id (from your Relay dashboard). Required. Matches the React
12
+ * `profileId` prop name. `appId` is the original alias — still works. */
6
13
  profileId?: string;
14
+ /** @deprecated alias for `profileId` — kept working, `profileId` is now the
15
+ * documented name (matches React). */
16
+ appId?: string;
7
17
  /** Relay server URL. Defaults to the hosted relay; set for self-hosted. */
8
18
  url?: string;
9
19
  apiUrl?: string;
@@ -12,27 +22,139 @@ export interface RelaySettings {
12
22
  * both for an anonymous visitor. See EMBED.md. */
13
23
  token?: string;
14
24
  userId?: string;
15
- /** Display info shown to agents (not identity). */
25
+ /** Called when a signed `token` is rejected (expired): return a fresh token
26
+ * from your backend to renew the session without a reload. Matches the
27
+ * React `refreshToken` prop. Only usable from `window.relaySettings` /
28
+ * `Relay('boot', ...)` (a function can't be expressed as an HTML
29
+ * attribute) — not available via `data-relay-*`. */
30
+ refreshToken?: () => Promise<string | null>;
31
+ /** Display info shown to agents (not identity) — matches the React
32
+ * `userName` / `userEmail` / `userAvatar` props. */
33
+ userName?: string;
34
+ userEmail?: string;
35
+ userAvatar?: string;
36
+ /** @deprecated flat aliases for `userName` / `userEmail` / `userAvatar` —
37
+ * kept working (the shipped Shopify integration used these names nested
38
+ * under `user`, see `user` below, which is the fix for that; these bare
39
+ * top-level fields predate that and still work standalone). */
16
40
  name?: string;
17
41
  email?: string;
18
42
  avatar?: string;
19
- /** Subject the chat is about (e.g. a marketplace listing). `listingId` is sugar
20
- * for `subjectId: "listing_<id>"`. */
43
+ /** Same info as `userName`/`userEmail`/`userAvatar`, as one nested object
44
+ * matches `MountOptions.user` / React's internal shape exactly, and is
45
+ * what a server-rendered snippet (e.g. Shopify Liquid, WordPress PHP) will
46
+ * most naturally emit: `user: { name: "...", email: "..." }`. Takes
47
+ * precedence over the flat fields if both are somehow given. */
48
+ user?: UserInfo;
49
+ /** Subject the chat is about (e.g. a marketplace listing). `listingId` is
50
+ * sugar for `subjectId: "listing_<id>"`. */
21
51
  subjectId?: string;
22
52
  listingId?: string;
53
+ /** Context-card fields — matches React's `ChatWidget` `contextTitle` /
54
+ * `contextSubtitle` / `contextStatus` props (a general "here's what this
55
+ * conversation is about" card: an order, ticket, booking, etc). */
56
+ contextTitle?: string;
57
+ contextSubtitle?: string;
58
+ contextStatus?: string;
59
+ /** Marketplace-card fields — matches React's `MarketplaceChat`
60
+ * `listingTitle` / `listingMeta` / `listingPrice` / `listingStatus` props
61
+ * (a specific-item card: price + status badge, e.g. "2019 Camry — $12,500
62
+ * — Available"). Use these OR `contextTitle`/etc — both build the same
63
+ * card, pick whichever vocabulary matches your use case. */
64
+ listingTitle?: string;
65
+ listingMeta?: string;
66
+ listingPrice?: number;
67
+ listingStatus?: string;
68
+ /** @deprecated original flat names for the context/marketplace card —
69
+ * kept working. `contextTitle`/`listingTitle` are now the documented
70
+ * names (matching the two React components). */
23
71
  subjectTitle?: string;
24
72
  subjectMeta?: string;
25
73
  subjectPrice?: number;
26
74
  subjectStatus?: string;
75
+ /** The context/marketplace card as one nested object, if you'd rather build
76
+ * it yourself than use the flat fields above — matches
77
+ * `MountOptions.subject` exactly. Takes precedence over every flat field
78
+ * above if given. */
79
+ subject?: {
80
+ title?: string;
81
+ subtitle?: string;
82
+ tags?: string[];
83
+ status?: string;
84
+ ownerLabel?: string;
85
+ };
86
+ /** Pre-set reply chips shown above the input — matches the React
87
+ * `quickReplies` prop. `window.relaySettings` / `Relay('boot', ...)` only
88
+ * (an array can't be expressed as a single `data-relay-*` attribute). */
89
+ quickReplies?: string[];
90
+ /** i18n string overrides — matches the React `i18n` prop. Same restriction
91
+ * as `quickReplies`: object, so JS-object form only. */
92
+ i18n?: MountOptions['i18n'];
27
93
  /** Appearance. `launcher` defaults to true (a floating bubble). */
28
94
  accent?: string;
29
95
  launcher?: boolean;
30
96
  position?: 'bottom-right' | 'bottom-left';
31
- /** Launcher teaser ("optional message" above the bubble). Title, or
32
- * title + subtitle. Omit to use the chatroom's manifest value. */
33
- launcherMessage?: string;
97
+ /** Launcher teaser ("optional message" above the bubble). Matches the React
98
+ * `launcherMessage` prop exactly: a bare string (title only), or
99
+ * `{ title, subtitle }`. `launcherSubtitle` below is a SEPARATE flat
100
+ * field kept only so `data-relay-launcher-message` /
101
+ * `data-relay-launcher-subtitle` (two HTML attributes — an attribute
102
+ * can't hold a nested object) can still combine into the same shape; in
103
+ * JS-object form just pass the object directly, same as React. Omit to
104
+ * use the chatroom's manifest value. */
105
+ launcherMessage?: string | {
106
+ title: string;
107
+ subtitle?: string;
108
+ };
109
+ /** @deprecated HTML-attribute-only companion to a string `launcherMessage`
110
+ * — see the note above. Prefer `launcherMessage: { title, subtitle }` in
111
+ * JS-object form. */
34
112
  launcherSubtitle?: string;
35
113
  translateLang?: string;
114
+ /** Mount INLINE into an existing element instead of the auto-created,
115
+ * body-appended host that the default floating launcher uses. A CSS
116
+ * selector string (works from `data-relay-target` too) or an element
117
+ * reference (JS-object form only). Ignored when `launcher` is true — same
118
+ * restriction as `height`/`inbox` below: a floating launcher panel is a
119
+ * fixed-size popup `mount()` owns, not something you place in the page. */
120
+ el?: string | HTMLElement;
121
+ /** Inline container height — matches the React `height` prop. Only applies
122
+ * when `launcher` is false/omitted. */
123
+ height?: string;
124
+ /** Adds a back-chevron to the widget that swaps it for the full
125
+ * conversation list — matches the React `ChatWidget`/`MarketplaceChat`
126
+ * `inbox` prop, INCLUDING its one limitation: not supported in launcher
127
+ * mode. (React itself requires a different component, `ChatAppLauncher`,
128
+ * for a launcher+inbox combination — same scope boundary here.) Tapping a
129
+ * row opens that conversation; its own back-chevron returns to the list; a
130
+ * ✕ in the list view returns to this widget's original single-thread
131
+ * view. Requires `launcher: false`. */
132
+ inbox?: boolean;
133
+ /** Open DIRECTLY on the conversation list instead of a single thread —
134
+ * what you want for a dedicated "Messages" page. `inbox: true` alone only
135
+ * adds a back-chevron to a single thread, so the list is reachable but
136
+ * never the landing view; that is the right default for a widget bolted
137
+ * onto a product page, and the wrong one for a page whose whole job is the
138
+ * inbox. Implies `inbox`. Requires `launcher: false` (same restriction as
139
+ * `inbox`), and `profileId` OR `tenantId`.
140
+ *
141
+ * With `inboxStart`, tapping a row opens that conversation and its own
142
+ * back-chevron returns to the list. There is no ✕ — the list IS the root
143
+ * view here, so there is nothing behind it to close back to. */
144
+ inboxStart?: boolean;
145
+ /** Inbox scope when `inbox` is set: `'tenant'` (default) lists the user's
146
+ * threads across ALL your chatrooms; `'profile'` limits it to this one. */
147
+ inboxScope?: 'tenant' | 'profile';
148
+ /** Your business id — lists the visitor's threads across ALL your chatrooms
149
+ * without naming one, and lets "new conversation" open against your default
150
+ * chatroom (the server reports it). This is the vanilla equivalent of
151
+ * React's `<ChatApp tenantId=… />`, which has always accepted a tenant with
152
+ * no profile; the script tag previously could not express that at all and
153
+ * hard-required a `profileId`.
154
+ *
155
+ * Only meaningful with `inboxStart` — a single-thread widget still needs a
156
+ * `profileId`, because a lone thread has to belong to a specific chatroom. */
157
+ tenantId?: string;
36
158
  }
37
159
  export type RelayCommand = 'boot' | 'update' | 'identify' | 'shutdown';
38
160
  /** The public command dispatcher exposed as `window.Relay`. */