nivq-chat-widget 1.0.0 → 1.0.1

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
@@ -5,14 +5,15 @@ Embeddable NivQ chat experience as a framework-agnostic Web Component
5
5
  one tag and get the NivQ chat UI in a floating launcher (FAB) or inline panel.
6
6
 
7
7
  - **Style-isolated** — renders in a Shadow DOM; no CSS bleed in either direction.
8
- - **Secret-safe** — never touches your OAuth2 `clientSecret`. It fetches a
9
- short-lived token from your own token-broker endpoint. See
10
- [`docs/integration.md`](docs/integration.md).
8
+ - **Secret-safe** — never touches your OAuth2 `clientSecret`; it uses a
9
+ short-lived token from your own token-broker endpoint (see below).
11
10
  - **Themeable** — defaults to the NivQ brand; override palette, logo, radius.
12
11
  - **Lazy charts** — the Vega rendering stack loads only when a turn returns a chart.
13
12
 
14
13
  ## Quick start
15
14
 
15
+ Via CDN (jsDelivr) — no build step:
16
+
16
17
  ```html
17
18
  <script type="module" src="https://cdn.jsdelivr.net/npm/nivq-chat-widget@1/dist/nivq-chat.js"></script>
18
19
  <nivq-chat
@@ -22,74 +23,79 @@ one tag and get the NivQ chat UI in a floating launcher (FAB) or inline panel.
22
23
  </nivq-chat>
23
24
  ```
24
25
 
25
- You must run a small **token broker** on your backend (≈20 lines) so the secret
26
- never reaches the browser. Full guide + Node/Spring examples:
27
- [`docs/integration.md`](docs/integration.md).
28
-
29
- ## Develop
26
+ Or via npm (bundlers / React / Angular):
30
27
 
31
28
  ```bash
32
- bun install
33
-
34
- # 1. Run the mock token broker against a NivQ backend + a dev API client:
35
- NIVQ_API_BASE=http://localhost:8080 \
36
- NIVQ_CLIENT_ID=nivq_… NIVQ_CLIENT_SECRET=… \
37
- bun run broker
38
-
39
- # 2. Run the dev harness, then fill in agent id + endpoints and click Mount:
40
- bun run dev
41
-
42
- bun run build # → dist/nivq-chat.js (+ lazy chart chunk)
43
- bun run type-check
29
+ npm i nivq-chat-widget
44
30
  ```
45
-
46
- ## Architecture
47
-
48
- ```
49
- <nivq-chat> (custom element, src/element.ts)
50
- └─ Shadow DOM + injected Tailwind stylesheet (tokens scoped to :host)
51
- └─ React root (src/Widget.tsx) — FAB or inline
52
- ├─ TokenProvider (src/auth) — fetches/caches the broker token
53
- ├─ useWidgetChat (src/chat) — SSE streaming, ephemeral/local state
54
- └─ chat UI (src/chat) — forked from nivq-web-client
31
+ ```ts
32
+ import 'nivq-chat-widget'; // registers <nivq-chat>; ships its own types
55
33
  ```
56
34
 
57
- Presentational chat components are forked from `nivq-web-client`; a shared
58
- `nivq-chat-ui` package is a possible future consolidation.
59
-
60
- ## Publish (maintainers)
61
-
62
- Published to npm; the CDN URL is served straight from npm by jsDelivr — one
63
- publish covers both `npm i` and `<script>` consumers.
64
-
65
- ### CI release (recommended)
66
-
67
- `.github/workflows/release.yml` publishes automatically when a `v*` tag is
68
- pushed. The flow:
69
-
70
- ```bash
71
- npm version <patch|minor|major> # bumps package.json + creates tag vX.Y.Z
72
- git push --follow-tags # CI builds (via prepublishOnly) and publishes
35
+ Pin the major (`@1`) for automatic patches without breaking changes.
36
+
37
+ ## Token broker (required)
38
+
39
+ The widget authenticates with a **short-lived bearer token**, never your
40
+ `clientSecret`. Stand up one small endpoint on your backend that exchanges your
41
+ NivQ credentials for a token and returns only `{ access_token, expires_in }`:
42
+
43
+ ```js
44
+ // POST /nivq-token (your backend) — authenticate YOUR user first, then:
45
+ const r = await fetch(`${process.env.NIVQ_API_BASE}/oauth2/token`, {
46
+ method: 'POST',
47
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
48
+ body: new URLSearchParams({
49
+ grant_type: 'client_credentials',
50
+ client_id: process.env.NIVQ_CLIENT_ID,
51
+ client_secret: process.env.NIVQ_CLIENT_SECRET, // stays server-side
52
+ }),
53
+ });
54
+ const { access_token, expires_in } = await r.json();
55
+ res.json({ access_token, expires_in }); // never return the secret
73
56
  ```
74
57
 
75
- One-time setup: add repo secret **`NPM_TOKEN`** (npm automation token with
76
- publish rights for the `nivq-chat-widget` package) under Settings Secrets and variables →
77
- Actions. `.github/workflows/ci.yml` runs type-check + build on every push/PR.
78
-
79
- ### Manual publish (fallback)
80
-
81
- ```bash
82
- npm login # your npm account with publish rights
83
- npm publish # prepublishOnly runs the build automatically
58
+ The widget calls `POST {token-endpoint}`, caches the token, and refreshes it
59
+ before expiry. Full Node/Spring examples, the CORS allow-list step, and the
60
+ privacy notes are in `docs/integration.md` (shipped in the package).
61
+
62
+ > **CORS:** the widget streams straight from the browser to `api-base-url`, so
63
+ > add the origin(s) you embed on to the API key's **Allowed origins** when you
64
+ > create it.
65
+
66
+ ## Configuration
67
+
68
+ | Attribute | Required | Default | Description |
69
+ |---|---|---|---|
70
+ | `agent-id` | ✅ | — | The NivQ agent to chat with |
71
+ | `token-endpoint` | ✅ | — | Your token broker URL |
72
+ | `api-base-url` | ✅ | — | NivQ chat API base (e.g. `https://api.nivq.ai`) |
73
+ | `mode` | | `fab` | `fab` (floating launcher) or `inline` |
74
+ | `target` | | — | Inline only: CSS selector of the container to mount into |
75
+ | `position` | | `bottom-right` | `bottom-right` or `bottom-left` (FAB) |
76
+ | `theme` | | `auto` | `light`, `dark`, or `auto` (follows OS) |
77
+ | `locale` | | `auto` | `tr`, `en`, or `auto` (browser language) |
78
+ | `primary-color` | | NivQ red | Hex (`#6d28d9`) or HSL triplet (`265 70% 50%`) |
79
+ | `accent-color` | | NivQ | Hex or HSL triplet |
80
+ | `radius` | | `0.625rem` | Corner radius (any CSS length) |
81
+ | `logo-url` | | NivQ mark | Replace the brand logo |
82
+ | `brand-name` | | `NivQ` | Header title / logo alt text |
83
+ | `launcher-label` | | — | Text next to the FAB icon |
84
+ | `greeting` | | — | Custom empty-state heading |
85
+ | `placeholder` | | localized | Input placeholder text |
86
+ | `title` | | localized | Panel header title |
87
+ | `external-user-id` | | — | Distinguishes your end-user |
88
+ | `persist` | | `false` | Persist the conversation in `localStorage` |
89
+ | `start-open` | | `false` | FAB only: open the panel on load |
90
+
91
+ Programmatic config:
92
+
93
+ ```js
94
+ document.querySelector('nivq-chat').configure({
95
+ primaryColor: '#6d28d9', brandName: 'Acme Assistant', mode: 'inline',
96
+ });
84
97
  ```
85
98
 
86
- After publish the CDN URL is live immediately:
87
-
88
- ```
89
- https://cdn.jsdelivr.net/npm/nivq-chat-widget@1/dist/nivq-chat.js
90
- ```
99
+ ## License
91
100
 
92
- `files: ["dist"]` ships only the build output (entry + chunks + `nivq-chat.d.ts`).
93
- Tell customers to pin the major (`@1`) — they get patches/minors, never breaking
94
- changes. Every release bumps `version` (immutable, long-cached URLs); the entry
95
- imports its chunks by relative path, so all `dist/*` files publish together.
101
+ UNLICENSED © Nivorbit. See https://nivq.ai.
@@ -0,0 +1,248 @@
1
+ # NivQ Chat Widget — Integration Guide
2
+
3
+ > 📖 **This guide is also published in the NivQ docs:** https://nivq.ai/en/docs/chat-widget
4
+ > (Turkish: https://nivq.ai/tr/docs/chat-widget). The docs version is the canonical,
5
+ > reader-friendly copy; this file is the in-repo reference for maintainers.
6
+
7
+ Embed the NivQ chat experience on any website or app with a single tag:
8
+
9
+ ```html
10
+ <script type="module" src="https://cdn.jsdelivr.net/npm/nivq-chat-widget@1/dist/nivq-chat.js"></script>
11
+ <nivq-chat
12
+ agent-id="YOUR_AGENT_ID"
13
+ api-base-url="https://api.nivq.ai"
14
+ token-endpoint="/nivq-token">
15
+ </nivq-chat>
16
+ ```
17
+
18
+ The widget is a framework-agnostic Web Component — it works the same in plain
19
+ HTML, React, Angular, Vue, or anything else. It renders inside a Shadow DOM, so
20
+ its styles never leak into your page and your page's CSS never breaks it.
21
+
22
+ ---
23
+
24
+ ## 1. Security model — read this first
25
+
26
+ What the "API Key" screen gives you is an **OAuth2 client credential pair**
27
+ (`clientId` + `clientSecret`), used with the `client_credentials` grant:
28
+
29
+ ```
30
+ clientId + clientSecret ──► POST /oauth2/token ──► short-lived access token (≈1h)
31
+ access token (Bearer) ──► POST /v1/agents/{agentId}/conversations (chat)
32
+ ```
33
+
34
+ > **The `clientSecret` is a long-lived root credential. Never put it in the
35
+ > browser, the widget, or any client-side code.** Anyone who extracts it can
36
+ > mint tokens and run up your usage.
37
+
38
+ The widget therefore **never** receives the secret. It only ever receives a
39
+ short-lived access token, fetched from **your own backend endpoint** (the
40
+ "token broker"). The secret stays on your server.
41
+
42
+ ```
43
+ Browser (widget) ──POST──► your /nivq-token ──client_credentials──► NivQ /oauth2/token
44
+ ▲ │
45
+ └──── { access_token } ◄────┘ (secret never returned to the browser)
46
+ ```
47
+
48
+ ---
49
+
50
+ ## 2. Build the token broker (≈20 lines)
51
+
52
+ The broker is one endpoint on your own backend. It (a) authenticates *your*
53
+ end-user with your own session, then (b) exchanges your NivQ secret for a token
54
+ and returns just `{ access_token, expires_in }`.
55
+
56
+ ### Node / Express
57
+
58
+ ```js
59
+ import express from 'express';
60
+
61
+ const app = express();
62
+
63
+ app.post('/nivq-token', async (req, res) => {
64
+ // 1. Authenticate YOUR user here (session/cookie/JWT). Reject if not logged in.
65
+ // if (!req.user) return res.sendStatus(401);
66
+
67
+ // 2. Exchange the secret for a short-lived token.
68
+ const r = await fetch(`${process.env.NIVQ_API_BASE}/oauth2/token`, {
69
+ method: 'POST',
70
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
71
+ body: new URLSearchParams({
72
+ grant_type: 'client_credentials',
73
+ client_id: process.env.NIVQ_CLIENT_ID,
74
+ client_secret: process.env.NIVQ_CLIENT_SECRET, // stays server-side
75
+ }),
76
+ });
77
+ if (!r.ok) return res.sendStatus(502);
78
+ const { access_token, expires_in } = await r.json();
79
+
80
+ // 3. Return ONLY the token. Never the secret.
81
+ res.json({ access_token, expires_in });
82
+ });
83
+ ```
84
+
85
+ ### Spring Boot
86
+
87
+ ```java
88
+ @RestController
89
+ class NivqTokenBroker {
90
+
91
+ private final RestClient client = RestClient.create();
92
+
93
+ @Value("${nivq.api-base}") String apiBase;
94
+ @Value("${nivq.client-id}") String clientId;
95
+ @Value("${nivq.client-secret}") String clientSecret; // stays server-side
96
+
97
+ @PostMapping("/nivq-token")
98
+ Map<String, Object> token(/* inject your authenticated principal here */) {
99
+ var form = new LinkedMultiValueMap<String, String>();
100
+ form.add("grant_type", "client_credentials");
101
+ form.add("client_id", clientId);
102
+ form.add("client_secret", clientSecret);
103
+
104
+ var resp = client.post()
105
+ .uri(apiBase + "/oauth2/token")
106
+ .contentType(MediaType.APPLICATION_FORM_URLENCODED)
107
+ .body(form)
108
+ .retrieve()
109
+ .body(Map.class);
110
+
111
+ // Return only the token + expiry — never the secret.
112
+ return Map.of("access_token", resp.get("access_token"),
113
+ "expires_in", resp.get("expires_in"));
114
+ }
115
+ }
116
+ ```
117
+
118
+ The widget calls `POST {token-endpoint}` and expects `{ access_token, expires_in }`
119
+ (seconds). It caches the token and refreshes automatically before it expires.
120
+
121
+ ---
122
+
123
+ ## 3. Embed the widget
124
+
125
+ ### Plain HTML / any site
126
+
127
+ ```html
128
+ <script type="module" src="https://cdn.jsdelivr.net/npm/nivq-chat-widget@1/dist/nivq-chat.js"></script>
129
+ <nivq-chat agent-id="…" api-base-url="https://api.nivq.ai" token-endpoint="/nivq-token"></nivq-chat>
130
+ ```
131
+
132
+ ### React
133
+
134
+ Install from npm (recommended for bundled apps):
135
+
136
+ ```bash
137
+ npm i nivq-chat-widget
138
+ ```
139
+
140
+ ```tsx
141
+ import 'nivq-chat-widget'; // registers <nivq-chat>; ships its own types
142
+
143
+ export function Support() {
144
+ return (
145
+ <nivq-chat
146
+ agent-id="…"
147
+ api-base-url="https://api.nivq.ai"
148
+ token-endpoint="/nivq-token"
149
+ />
150
+ );
151
+ }
152
+ ```
153
+
154
+ > The package ships type definitions, so `<nivq-chat>` is typed out of the box.
155
+ > On React 19's strict JSX (no global `JSX` namespace), add a one-line module
156
+ > augmentation if your editor flags the tag:
157
+ > ```ts
158
+ > declare module 'react' {
159
+ > namespace JSX { interface IntrinsicElements { 'nivq-chat': any } }
160
+ > }
161
+ > ```
162
+
163
+ ### Angular
164
+
165
+ ```ts
166
+ // app.module.ts — allow custom elements
167
+ import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
168
+ @NgModule({ schemas: [CUSTOM_ELEMENTS_SCHEMA] })
169
+ export class AppModule {}
170
+ ```
171
+ ```html
172
+ <nivq-chat agent-id="…" api-base-url="https://api.nivq.ai" token-endpoint="/nivq-token"></nivq-chat>
173
+ ```
174
+
175
+ ---
176
+
177
+ ## 4. Configuration reference
178
+
179
+ | Attribute | Required | Default | Description |
180
+ |---|---|---|---|
181
+ | `agent-id` | ✅ | — | The NivQ agent to chat with |
182
+ | `token-endpoint` | ✅ | — | Your token broker URL |
183
+ | `api-base-url` | ✅ | — | NivQ chat API base (e.g. `https://api.nivq.ai`) |
184
+ | `mode` | | `fab` | `fab` (floating launcher) or `inline` |
185
+ | `target` | | — | Inline only: CSS selector of the container to mount into |
186
+ | `position` | | `bottom-right` | `bottom-right` or `bottom-left` (FAB) |
187
+ | `theme` | | `auto` | `light`, `dark`, or `auto` (follows OS) |
188
+ | `locale` | | `auto` | `tr`, `en`, or `auto` (browser language) |
189
+ | `primary-color` | | NivQ red | Hex (`#6d28d9`) or HSL triplet (`265 70% 50%`) |
190
+ | `accent-color` | | NivQ | Hex or HSL triplet |
191
+ | `radius` | | `0.625rem` | Corner radius (any CSS length) |
192
+ | `logo-url` | | NivQ mark | Replace the brand logo |
193
+ | `brand-name` | | `NivQ` | Header title / logo alt text |
194
+ | `launcher-label` | | — | Text next to the FAB icon |
195
+ | `greeting` | | — | Custom empty-state heading |
196
+ | `placeholder` | | localized | Input placeholder text |
197
+ | `title` | | localized | Panel header title |
198
+ | `external-user-id` | | — | Distinguishes your end-user (analytics/isolation) |
199
+ | `persist` | | `false` | Persist the conversation in `localStorage` |
200
+ | `start-open` | | `false` | FAB only: open the panel on load |
201
+
202
+ ### Programmatic API
203
+
204
+ ```js
205
+ const el = document.querySelector('nivq-chat');
206
+ el.configure({ primaryColor: '#6d28d9', brandName: 'Acme Assistant', mode: 'inline' });
207
+ ```
208
+
209
+ ### Theming
210
+
211
+ Defaults are the NivQ brand (logo + red palette). Override the key tokens and
212
+ the rest derive from them:
213
+
214
+ ```html
215
+ <nivq-chat … primary-color="#6d28d9" accent-color="#f59e0b" radius="14px"
216
+ logo-url="https://acme.com/logo.svg" brand-name="Acme Assistant"></nivq-chat>
217
+ ```
218
+
219
+ ---
220
+
221
+ ## 5. Allow your embedding origin (CORS)
222
+
223
+ The widget streams **directly** from the browser to your `api-base-url` (the
224
+ token broker only mints tokens), so the NivQ API must allow your site's origin.
225
+
226
+ **Set it on the API key.** When you create the API key (the screen that gives
227
+ you the clientId + clientSecret), list every web origin the widget is embedded
228
+ on under **Allowed origins** — e.g. `https://app.acme.com`. Origins are
229
+ `scheme://host[:port]` with no path or wildcard, matching what the browser
230
+ sends. Leaving it empty disables browser embedding for that key (server-to-server
231
+ use only). Changes take effect within a minute.
232
+
233
+ > **On-prem:** point `api-base-url` at your own NivQ deployment. The same
234
+ > per-key Allowed origins apply. A deployment-wide alternative is the
235
+ > `cors.allowed-origin-patterns` setting in your NivQ config, if you prefer to
236
+ > manage embedding domains centrally rather than per key.
237
+
238
+ Without an allowed origin, the browser blocks the stream with a CORS error.
239
+
240
+ ---
241
+
242
+ ## 6. Privacy note
243
+
244
+ A single API client is shared across all end-users of your integration, and
245
+ conversation history is scoped to the client — not to an individual end-user.
246
+ The widget therefore keeps each session local (in-memory, or `localStorage`
247
+ when `persist` is on) and does **not** load shared history. Use
248
+ `external-user-id` to distinguish your end-users on the NivQ side.
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "nivq-chat-widget",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "type": "module",
5
5
  "description": "Embeddable NivQ chat web component (<nivq-chat>) for any website or framework.",
6
6
  "license": "UNLICENSED",
7
- "homepage": "https://github.com/nivorbit/nivq-chat-widget#readme",
7
+ "homepage": "https://nivq.ai",
8
8
  "keywords": ["nivq", "chat", "widget", "web-component", "custom-element", "ai", "embed"],
9
9
  "module": "./dist/nivq-chat.js",
10
10
  "types": "./dist/nivq-chat.d.ts",
@@ -14,7 +14,7 @@
14
14
  "import": "./dist/nivq-chat.js"
15
15
  }
16
16
  },
17
- "files": ["dist"],
17
+ "files": ["dist", "docs"],
18
18
  "sideEffects": ["./dist/**"],
19
19
  "publishConfig": {
20
20
  "access": "public"