@primexperts.co/pulse-agent 5.0.0 → 5.2.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 CHANGED
@@ -1,97 +1,445 @@
1
1
  # @primexperts.co/pulse-agent
2
2
 
3
- Embeddable floating Pulse chat widget for client websites.
3
+ Pulse Agent is a browser-only customer support widget for adding Pulse Webchat and optional website error reporting to any public website, SPA, or client-rendered application.
4
4
 
5
- ## Documentation
5
+ It is designed for teams that want one lightweight install to do two jobs:
6
6
 
7
- - Adds a floating webchat launcher to any browser-based website or SPA.
8
- - Resolves the active Pulse webchat configuration by organization slug.
9
- - Sends visitor messages over public REST endpoints and receives agent replies over Server-Sent Events.
10
- - Supports optional pre-chat profile capture, conversation history loading, transcript email requests, and manual open/close controls.
11
- - Works with Angular, React, Vue, Next.js/Nuxt client components, static sites, and plain browser JavaScript.
7
+ - Add a floating Pulse chat launcher to a website.
8
+ - Report browser errors and failed API calls back to Pulse when error monitoring is enabled.
12
9
 
13
- ## Install
10
+ The package is framework-agnostic. It works with Angular, React, Vue, static HTML, and SSR frameworks when initialized on the client side.
11
+
12
+ ## Quick Start
14
13
 
15
14
  ```bash
16
- npm install '@primexperts.co/pulse-agent@latest'
15
+ npm install @primexperts.co/pulse-agent@latest
17
16
  ```
18
17
 
19
- ## Usage
20
-
21
18
  ```ts
22
19
  import { createPulseAgent } from '@primexperts.co/pulse-agent';
23
20
 
24
21
  const agent = createPulseAgent({
25
- slug: 'client-abc',
22
+ slug: 'your-organization-slug',
23
+ appKey: 'app_your_public_application_key',
26
24
  apiBaseUrl: 'https://pulse.service.primexperts.co.za',
25
+ title: 'Support',
27
26
  requireProfile: true,
28
- autoOpen: false
27
+ autoOpen: false,
28
+ errorMonitoring: true,
29
+ captureUnhandledErrors: true,
30
+ captureUnhandledRejections: true,
31
+ captureFetchErrors: true,
32
+ environment: 'production',
33
+ release: 'web-1.0.0'
29
34
  });
30
35
 
31
- // optional
32
36
  agent.open();
33
37
  ```
34
38
 
35
- ## Browser/CDN Usage (No Bundler)
39
+ ## What You Need From Pulse
40
+
41
+ You need these values before going live:
42
+
43
+ | Value | Example | Required | Where it comes from |
44
+ | --- | --- | --- | --- |
45
+ | Organization slug | `vayakids` | Yes | Pulse organization/webchat setup |
46
+ | Application key | `app_...` | Recommended | Pulse Dashboard application/webchat setup |
47
+ | API base URL | `https://pulse.service.primexperts.co.za` | Yes | Pulse production API host |
48
+ | Allowed website origin | `https://www.example.com` | Yes | Must be allowed by Pulse CORS/backend configuration |
49
+
50
+ You usually do not need a widget token. The widget resolves it automatically by calling Pulse with your `slug` and `appKey`.
51
+
52
+ ## Configuration Keys
53
+
54
+ | Option | Type | Default | Description |
55
+ | --- | --- | --- | --- |
56
+ | `slug` | `string` | Required | Public organization slug used to resolve the active webchat widget. |
57
+ | `appKey` | `string` | Optional but recommended | Public application key, normally starts with `app_`. Use this for application-specific setup and error attribution. |
58
+ | `appSlug` | `string` | Optional | Alternative application identifier when an app key is not available. |
59
+ | `apiBaseUrl` | `string` | `https://pulse.service.primexperts.co.za` | Pulse API host only. Do not include `/api/...` paths. |
60
+ | `title` | `string` | Organization/app name | Chat panel title. |
61
+ | `mount` | `HTMLElement \| string` | `document.body` | Element or selector where the widget root should be mounted. |
62
+ | `autoOpen` | `boolean` | `false` | Opens the chat panel immediately after initialization. |
63
+ | `requireProfile` | `boolean` | `true` | Requires visitor name/email before first message. |
64
+ | `followUpEmailTo` | `string` | Optional | Destination used by transcript/follow-up email actions. |
65
+ | `dashboardUrl` | `string` | `https://pulse.primexperts.co.za` | Dashboard link used in setup/error states. |
66
+ | `dashboardSetupPath` | `string` | `/integrations` | Dashboard path linked when setup is incomplete. |
67
+ | `whatsappUrl` | `string` | Optional | Adds a WhatsApp launcher alongside Pulse Chat. |
68
+ | `widgetToken` | `string` | Optional | Advanced bypass for resolve flow. Leave unset unless Pulse gives you a specific token. |
69
+ | `errorMonitoring` | `boolean` | `false` | Enables browser error reporting. Must be `true` before any capture happens. |
70
+ | `captureUnhandledErrors` | `boolean` | `true` when monitoring is enabled | Captures `window.onerror` events. |
71
+ | `captureUnhandledRejections` | `boolean` | `true` when monitoring is enabled | Captures unhandled promise rejections. |
72
+ | `captureFetchErrors` | `boolean` | `true` when monitoring is enabled | Wraps browser `fetch` to report failed requests and thrown network errors. |
73
+ | `environment` | `string` | Optional | Environment label such as `production`, `staging`, or `development`. |
74
+ | `release` | `string` | Optional | Application release/version label for error context. |
75
+
76
+ ## Environment Variables
77
+
78
+ Pulse Agent itself receives JavaScript options. For deployed apps, put values in your platform environment and generate or serve a runtime config file that passes those values to `createPulseAgent`.
79
+
80
+ Recommended names:
81
+
82
+ ```env
83
+ PULSE_AGENT_SLUG=your-organization-slug
84
+ PULSE_AGENT_APP_KEY=app_your_public_application_key
85
+ PULSE_AGENT_API_BASE_URL=https://pulse.service.primexperts.co.za
86
+ PULSE_AGENT_TITLE=Support
87
+ PULSE_AGENT_ENVIRONMENT=production
88
+ PULSE_AGENT_RELEASE=web-1.0.0
89
+
90
+ PULSE_AGENT_ERROR_MONITORING=true
91
+ PULSE_AGENT_CAPTURE_UNHANDLED_ERRORS=true
92
+ PULSE_AGENT_CAPTURE_UNHANDLED_REJECTIONS=true
93
+ PULSE_AGENT_CAPTURE_FETCH_ERRORS=true
94
+ ```
95
+
96
+ Optional names:
97
+
98
+ ```env
99
+ PULSE_AGENT_FOLLOW_UP_EMAIL_TO=support@example.com
100
+ PULSE_AGENT_DASHBOARD_URL=https://pulse.primexperts.co.za
101
+ PULSE_AGENT_DASHBOARD_SETUP_PATH=/integrations
102
+ PULSE_AGENT_AUTO_OPEN=false
103
+ PULSE_AGENT_REQUIRE_PROFILE=true
104
+ PULSE_AGENT_WHATSAPP_URL=https://api.whatsapp.com/send?phone=27123456789
105
+ PULSE_AGENT_WIDGET_TOKEN=
106
+ ```
107
+
108
+ Leave `PULSE_AGENT_WIDGET_TOKEN` unset unless Pulse Support explicitly gives you one. It is not the `app_*` key.
109
+
110
+ ## Heroku Config Vars
111
+
112
+ Set these in Heroku under Settings -> Config Vars, or with the Heroku CLI:
113
+
114
+ ```bash
115
+ heroku config:set PULSE_AGENT_SLUG=your-organization-slug
116
+ heroku config:set PULSE_AGENT_APP_KEY=app_your_public_application_key
117
+ heroku config:set PULSE_AGENT_API_BASE_URL=https://pulse.service.primexperts.co.za
118
+ heroku config:set PULSE_AGENT_TITLE=Support
119
+ heroku config:set PULSE_AGENT_ENVIRONMENT=production
120
+ heroku config:set PULSE_AGENT_ERROR_MONITORING=true
121
+ heroku config:set PULSE_AGENT_CAPTURE_UNHANDLED_ERRORS=true
122
+ heroku config:set PULSE_AGENT_CAPTURE_UNHANDLED_REJECTIONS=true
123
+ heroku config:set PULSE_AGENT_CAPTURE_FETCH_ERRORS=true
124
+ ```
125
+
126
+ If your app uses a `runtime-config.js` file, make sure Heroku serves that file from environment values at request time or regenerates it during build/startup.
127
+
128
+ ## Runtime Config Pattern
129
+
130
+ A common production-safe pattern is to load `/runtime-config.js` before your app bundle:
131
+
132
+ ```html
133
+ <script src="/runtime-config.js"></script>
134
+ ```
135
+
136
+ Example generated file:
137
+
138
+ ```js
139
+ window.__APP_RUNTIME_CONFIG__ = {
140
+ pulseAgent: {
141
+ slug: 'your-organization-slug',
142
+ appKey: 'app_your_public_application_key',
143
+ apiBaseUrl: 'https://pulse.service.primexperts.co.za',
144
+ title: 'Support',
145
+ errorMonitoring: true,
146
+ captureUnhandledErrors: true,
147
+ captureUnhandledRejections: true,
148
+ captureFetchErrors: true,
149
+ environment: 'production'
150
+ }
151
+ };
152
+ ```
153
+
154
+ Then initialize in your app entry point:
155
+
156
+ ```ts
157
+ import { createPulseAgent, type PulseAgentOptions } from '@primexperts.co/pulse-agent';
158
+
159
+ declare global {
160
+ interface Window {
161
+ __APP_RUNTIME_CONFIG__?: {
162
+ pulseAgent?: Partial<PulseAgentOptions>;
163
+ };
164
+ }
165
+ }
166
+
167
+ const config = window.__APP_RUNTIME_CONFIG__?.pulseAgent;
168
+
169
+ if (config?.slug) {
170
+ createPulseAgent({
171
+ ...config,
172
+ slug: config.slug
173
+ });
174
+ }
175
+ ```
176
+
177
+ ## Browser/CDN Usage
178
+
179
+ Use this when you cannot install npm packages:
36
180
 
37
181
  ```html
38
182
  <script src="https://unpkg.com/@primexperts.co/pulse-agent@latest/dist/browser/pulse-agent.global.js"></script>
39
183
  <script>
40
- const agent = window.PulseAgent.createPulseAgent({
41
- slug: 'client-abc',
42
- apiBaseUrl: 'https://pulse.service.primexperts.co.za'
184
+ window.PulseAgent.createPulseAgent({
185
+ slug: 'your-organization-slug',
186
+ appKey: 'app_your_public_application_key',
187
+ apiBaseUrl: 'https://pulse.service.primexperts.co.za',
188
+ title: 'Support',
189
+ errorMonitoring: true
43
190
  });
44
191
  </script>
45
192
  ```
46
193
 
47
- ## Required Backend Endpoints
194
+ ## Framework Examples
195
+
196
+ ### Angular
197
+
198
+ Initialize in `main.ts` or a browser-only root component. For SSR, guard against server execution.
199
+
200
+ ```ts
201
+ import { bootstrapApplication } from '@angular/platform-browser';
202
+ import { createPulseAgent } from '@primexperts.co/pulse-agent';
203
+ import { AppComponent } from './app/app.component';
204
+
205
+ if (typeof window !== 'undefined') {
206
+ createPulseAgent({
207
+ slug: 'your-organization-slug',
208
+ appKey: 'app_your_public_application_key',
209
+ apiBaseUrl: 'https://pulse.service.primexperts.co.za',
210
+ errorMonitoring: true
211
+ });
212
+ }
213
+
214
+ bootstrapApplication(AppComponent);
215
+ ```
216
+
217
+ ### React
218
+
219
+ ```tsx
220
+ import { useEffect } from 'react';
221
+ import { createPulseAgent } from '@primexperts.co/pulse-agent';
48
222
 
49
- - `GET /api/pulse/public/webchat/resolve?slug=<slug>`
50
- - `POST /api/pulse/public/webchat/{widgetToken}/message`
51
- - `GET /api/pulse/public/webchat/{widgetToken}/messages?conversationId=<id>`
52
- - `POST /api/pulse/public/webchat/{widgetToken}/transcript-email?conversationId=<id>`
53
- - `GET /api/pulse/public/webchat/{widgetToken}/stream?conversationId=<id>`
223
+ export function PulseAgentBootstrap() {
224
+ useEffect(() => {
225
+ const agent = createPulseAgent({
226
+ slug: 'your-organization-slug',
227
+ appKey: 'app_your_public_application_key',
228
+ apiBaseUrl: 'https://pulse.service.primexperts.co.za',
229
+ errorMonitoring: true
230
+ });
54
231
 
55
- ## API
232
+ return () => agent.destroy();
233
+ }, []);
56
234
 
57
- ### `createPulseAgent(options)`
235
+ return null;
236
+ }
237
+ ```
58
238
 
59
- Options:
60
- - `slug` (required): public organization slug.
61
- - `apiBaseUrl` (optional): defaults to `https://pulse.service.primexperts.co.za`.
62
- - `followUpEmailTo` (optional): preferred destination for transcript follow-up emails.
63
- - `mount` (optional): container element or selector, defaults to `document.body`.
64
- - `autoOpen` (optional): open panel immediately.
65
- - `requireProfile` (optional): requires name/email before first message, default `true`.
66
- - `widgetToken` (optional): bypass slug resolution.
67
- - `title` (optional): panel title.
239
+ ### Vue
68
240
 
69
- Returns `PulseAgentHandle`:
70
- - `open()`
71
- - `close()`
72
- - `destroy()`
73
- - `update(partialOptions)`
241
+ ```ts
242
+ import { onBeforeUnmount, onMounted } from 'vue';
243
+ import { createPulseAgent, type PulseAgentHandle } from '@primexperts.co/pulse-agent';
244
+
245
+ let agent: PulseAgentHandle | null = null;
246
+
247
+ onMounted(() => {
248
+ agent = createPulseAgent({
249
+ slug: 'your-organization-slug',
250
+ appKey: 'app_your_public_application_key',
251
+ apiBaseUrl: 'https://pulse.service.primexperts.co.za',
252
+ errorMonitoring: true
253
+ });
254
+ });
255
+
256
+ onBeforeUnmount(() => agent?.destroy());
257
+ ```
74
258
 
75
- ## Notes
259
+ ## How Widget Resolution Works
76
260
 
77
- - This package is framework-agnostic and browser-only.
78
- - No dashboard/auth code is included in this project.
79
- - If widget keys cannot be resolved, the chat shows an error message and retries resolution on the next send attempt.
80
- - `npm run build` now outputs:
81
- - ESM + type declarations in `dist/`
82
- - browser global bundle in `dist/browser/pulse-agent.global.js`
261
+ At startup or first send, Pulse Agent calls:
262
+
263
+ ```text
264
+ GET /api/pulse/public/webchat/resolve?slug=<slug>&appKey=<app_...>
265
+ ```
266
+
267
+ Pulse returns the active widget configuration, including an internal `widgetToken`. The widget then uses that token for chat, history, streaming, transcript email, and error reporting.
268
+
269
+ Do not confuse these values:
270
+
271
+ | Name | Format | Purpose | User action |
272
+ | --- | --- | --- | --- |
273
+ | `appKey` | `app_...` | Public application key from Pulse Dashboard | Put in config/env. |
274
+ | `widgetToken` | Token generated by Pulse | Internal public webchat token used by endpoints | Usually leave unset. |
275
+ | `slug` | human-readable slug | Identifies organization | Put in config/env. |
276
+
277
+ ## Error Monitoring
278
+
279
+ Error monitoring is opt-in. Installing the package does not capture anything until `errorMonitoring: true` is passed to `createPulseAgent`.
280
+
281
+ When enabled, Pulse Agent can capture:
282
+
283
+ - Unhandled JavaScript errors from `window.onerror`.
284
+ - Unhandled promise rejections from `window.onunhandledrejection`.
285
+ - Failed `fetch` responses, including HTTP statuses from failed API calls.
286
+ - Network errors thrown by `fetch`.
287
+ - Manual error reports through `agent.captureException(...)`.
288
+
289
+ Manual capture example:
290
+
291
+ ```ts
292
+ try {
293
+ await saveOrder();
294
+ } catch (error) {
295
+ agent.captureException(error, {
296
+ source: 'checkout.saveOrder',
297
+ errorType: 'checkout-error',
298
+ endpoint: '/api/orders',
299
+ pageUrl: window.location.href
300
+ });
301
+ throw error;
302
+ }
303
+ ```
304
+
305
+ Report count example:
306
+
307
+ ```ts
308
+ const reportsLastSevenDays = agent.getErrorReportCount(7);
309
+ ```
310
+
311
+ ### What Error Monitoring Does Not Do
312
+
313
+ Pulse Agent does not:
314
+
315
+ - Catch errors before the widget is initialized.
316
+ - Catch server-side errors unless those errors are exposed to the browser as failed requests.
317
+ - Replace backend logging, APM, or infrastructure monitoring.
318
+ - Read private app state, passwords, tokens, or request bodies intentionally.
319
+ - Automatically fix errors.
320
+ - Capture errors if `errorMonitoring` is missing or `false`.
321
+
322
+ ## Public Backend Endpoints
323
+
324
+ The widget expects these Pulse endpoints:
325
+
326
+ ```text
327
+ GET /api/pulse/public/webchat/resolve?slug=<slug>&appKey=<appKey>
328
+ POST /api/pulse/public/webchat/{widgetToken}/message
329
+ GET /api/pulse/public/webchat/{widgetToken}/messages?conversationId=<id>
330
+ POST /api/pulse/public/webchat/{widgetToken}/transcript-email?conversationId=<id>
331
+ GET /api/pulse/public/webchat/{widgetToken}/stream?conversationId=<id>
332
+ POST /api/pulse/public/webchat/{widgetToken}/errors
333
+ ```
334
+
335
+ Admin setup endpoints are separate and should remain authenticated.
336
+
337
+ ## Benefits
338
+
339
+ - One package for webchat and optional browser error monitoring.
340
+ - Framework-agnostic browser integration.
341
+ - Works with npm or a script tag.
342
+ - Supports runtime configuration, which is useful for Heroku, Docker, static hosting, and multi-environment deployments.
343
+ - Uses `slug` + `appKey` so users do not need to handle internal widget tokens.
344
+ - Supports visitor profile capture, history loading, transcript email requests, SSE replies, WhatsApp launcher, manual open/close, and manual exception reporting.
345
+
346
+ ## Limitations
347
+
348
+ - Browser-only. Initialize after mount/hydration in SSR frameworks.
349
+ - Requires the website origin to be allowed by Pulse CORS configuration.
350
+ - Requires an active Pulse Webchat integration for the organization/application.
351
+ - Requires `fetch` and `EventSource` support for full behavior.
352
+ - SSE can be affected by proxies, corporate networks, browser extensions, and load balancer timeouts.
353
+ - Error monitoring starts only after initialization and only when enabled.
354
+ - `fetch` monitoring covers `window.fetch`; it does not automatically wrap Axios internals unless Axios uses `fetch` in that environment.
355
+ - `widgetToken` is an advanced bypass and should not be used unless Pulse explicitly provides it.
356
+
357
+ ## Production Checklist
358
+
359
+ Before publishing a site with Pulse Agent:
360
+
361
+ 1. Confirm Pulse Webchat is enabled in the Pulse Dashboard.
362
+ 2. Confirm you have the correct `slug`.
363
+ 3. Confirm you have the correct `appKey` beginning with `app_`.
364
+ 4. Confirm `apiBaseUrl` is `https://pulse.service.primexperts.co.za`.
365
+ 5. Confirm production origin is allowed by Pulse CORS configuration.
366
+ 6. Confirm `runtime-config.js` or app config is generated from deployment env values.
367
+ 7. Confirm `errorMonitoring` is `true` if you expect browser error reporting.
368
+ 8. Open the site and check browser network calls for `resolve`, `message`, `stream`, and `errors` when applicable.
369
+ 9. Send a test chat message.
370
+ 10. Trigger a test error in a non-production or controlled environment and confirm it appears in Pulse.
371
+
372
+ ## Handle API
373
+
374
+ `createPulseAgent(options)` returns:
375
+
376
+ | Method | Description |
377
+ | --- | --- |
378
+ | `open()` | Opens the chat panel. |
379
+ | `close()` | Closes the chat panel. |
380
+ | `destroy()` | Removes DOM, listeners, and fetch wrapping installed by this instance. |
381
+ | `resetConversation()` | Starts a fresh local conversation. |
382
+ | `update(partialOptions)` | Updates runtime options such as title, WhatsApp URL, or widget token. |
383
+ | `captureException(error, context)` | Manually reports an error when monitoring is enabled. |
384
+ | `getErrorReportCount(days)` | Returns locally remembered successful error report count for the last N days. |
385
+
386
+ ## Troubleshooting
387
+
388
+ ### The widget does not appear
389
+
390
+ Check that initialization runs in the browser, the `slug` is present, and the mount selector exists.
391
+
392
+ ### `Failed to resolve widget keys (401/403)`
393
+
394
+ The public resolve endpoint is protected or the key is not accepted. Confirm the Pulse public webchat routes are accessible and the `appKey` is correct.
395
+
396
+ ### `Failed to resolve widget keys (404)`
397
+
398
+ The slug is wrong, webchat is not enabled, or the selected application has no active widget.
399
+
400
+ ### CORS error in the browser console
401
+
402
+ The website origin is missing from Pulse CORS configuration. Add the exact origin, including protocol and port.
403
+
404
+ ### Chat sends but replies do not stream
405
+
406
+ Check that `EventSource` is available and that proxies/load balancers allow long-lived SSE connections.
407
+
408
+ ### Errors are not captured
409
+
410
+ Confirm all of the following:
411
+
412
+ - `errorMonitoring: true` is present in runtime config.
413
+ - The widget successfully resolves a widget token.
414
+ - The error happens after `createPulseAgent(...)` runs.
415
+ - The browser can post to `/api/pulse/public/webchat/{widgetToken}/errors`.
416
+
417
+ ## Additional Documentation
418
+
419
+ - `docs/IMPLEMENTATION-GUIDE.md` for end-to-end setup examples.
420
+ - `docs/TROUBLESHOOTING.md` for diagnostics.
421
+ - `docs/FAQ.md` for common questions.
422
+ - `docs/INTEGRATION-CHANNELS.md` for channel-level implementation notes.
423
+
424
+ ## Build and Publish
425
+
426
+ ```bash
427
+ npm run build
428
+ npm publish --access public
429
+ ```
430
+
431
+ Build output includes:
432
+
433
+ - ESM and type declarations in `dist/`.
434
+ - Browser global bundle in `dist/browser/pulse-agent.global.js`.
83
435
 
84
436
  ## Versioning
85
437
 
86
- Use the built-in version script instead of editing files manually:
438
+ Use the built-in version script instead of editing package files manually:
87
439
 
88
440
  ```bash
89
441
  npm run version:patch
90
- # or
91
442
  npm run version:minor
92
443
  npm run version:major
93
- # or custom semver
94
444
  npm run version:bump -- 1.2.3
95
- ```
96
-
97
- This updates both `package.json` and `package-lock.json`.
445
+ ```
@@ -466,7 +466,7 @@ function ensureStyle() {
466
466
  }
467
467
  function createState(options) {
468
468
  const normalized = normalizeOptions(options);
469
- const persisted = loadPersistedSession(normalized.slug);
469
+ const persisted = loadPersistedSession(getAgentScopeKey(normalized));
470
470
  const persistedMessages = Array.isArray(persisted.messages)
471
471
  ? persisted.messages.filter((m) => !!m && (m.role === 'visitor' || m.role === 'agent' || m.role === 'system') && typeof m.content === 'string')
472
472
  : [];
@@ -496,6 +496,8 @@ function normalizeOptions(options) {
496
496
  return {
497
497
  ...options,
498
498
  slug: options.slug.trim(),
499
+ appKey: options.appKey?.trim() || undefined,
500
+ appSlug: options.appSlug?.trim() || undefined,
499
501
  apiBaseUrl: options.apiBaseUrl?.trim() || DEFAULT_API_BASE_URL,
500
502
  followUpEmailTo: options.followUpEmailTo?.trim() || '',
501
503
  whatsappUrl: '',
@@ -560,7 +562,14 @@ async function resolveWidgetToken(state, log, whatsappBtn, fabs, launcherBtn) {
560
562
  return;
561
563
  }
562
564
  try {
563
- const url = `${trimSlash(state.options.apiBaseUrl)}/api/pulse/public/webchat/resolve?slug=${encodeURIComponent(state.options.slug)}`;
565
+ const params = new URLSearchParams({ slug: state.options.slug });
566
+ if (state.options.appKey) {
567
+ params.set('appKey', state.options.appKey);
568
+ }
569
+ if (state.options.appSlug) {
570
+ params.set('appSlug', state.options.appSlug);
571
+ }
572
+ const url = `${trimSlash(state.options.apiBaseUrl)}/api/pulse/public/webchat/resolve?${params.toString()}`;
564
573
  const res = await fetch(url);
565
574
  if (!res.ok) {
566
575
  if (res.status === 401 || res.status === 403) {
@@ -877,7 +886,7 @@ function rememberErrorReport(state) {
877
886
  if (typeof localStorage === 'undefined') {
878
887
  return;
879
888
  }
880
- const key = getErrorReportStorageKey(state.options.slug);
889
+ const key = getErrorReportStorageKey(getAgentScopeKey(state.options));
881
890
  const rows = loadErrorReportTimes(state).concat(Date.now()).slice(-200);
882
891
  localStorage.setItem(key, JSON.stringify(rows));
883
892
  }
@@ -894,7 +903,7 @@ function loadErrorReportTimes(state) {
894
903
  if (typeof localStorage === 'undefined') {
895
904
  return [];
896
905
  }
897
- const raw = localStorage.getItem(getErrorReportStorageKey(state.options.slug));
906
+ const raw = localStorage.getItem(getErrorReportStorageKey(getAgentScopeKey(state.options)));
898
907
  if (!raw) {
899
908
  return [];
900
909
  }
@@ -908,6 +917,10 @@ function loadErrorReportTimes(state) {
908
917
  function getErrorReportStorageKey(slug) {
909
918
  return `pulse-agent-errors:${slug.toLowerCase()}`;
910
919
  }
920
+ function getAgentScopeKey(options) {
921
+ const app = options.appKey || options.appSlug || 'default';
922
+ return `${options.slug}:${app}`;
923
+ }
911
924
  function toggleFabActions(fabs, launcherBtn) {
912
925
  const collapsed = fabs.classList.toggle('collapsed');
913
926
  launcherBtn.setAttribute('aria-expanded', String(!collapsed));
@@ -948,15 +961,15 @@ function escapeHtml(input) {
948
961
  .replace(/"/g, '&quot;')
949
962
  .replace(/'/g, '&#39;');
950
963
  }
951
- function getStorageKey(slug) {
952
- return `pulse-agent-session:${slug.toLowerCase()}`;
964
+ function getStorageKey(scopeKey) {
965
+ return `pulse-agent-session:${scopeKey.toLowerCase()}`;
953
966
  }
954
- function loadPersistedSession(slug) {
967
+ function loadPersistedSession(scopeKey) {
955
968
  try {
956
969
  if (typeof localStorage === 'undefined') {
957
970
  return {};
958
971
  }
959
- const raw = localStorage.getItem(getStorageKey(slug));
972
+ const raw = localStorage.getItem(getStorageKey(scopeKey));
960
973
  if (!raw) {
961
974
  return {};
962
975
  }
@@ -979,7 +992,7 @@ function persistSession(state) {
979
992
  profile: state.profile,
980
993
  messages: state.messages.slice(-100)
981
994
  };
982
- localStorage.setItem(getStorageKey(state.options.slug), JSON.stringify(payload));
995
+ localStorage.setItem(getStorageKey(getAgentScopeKey(state.options)), JSON.stringify(payload));
983
996
  }
984
997
  catch {
985
998
  return;
@@ -1022,6 +1035,9 @@ function buildTranscriptBody(state) {
1022
1035
  const lines = [];
1023
1036
  lines.push(`Conversation ID: ${state.conversationId ?? 'N/A'}`);
1024
1037
  lines.push(`Organization slug: ${state.options.slug}`);
1038
+ if (state.options.appKey || state.options.appSlug) {
1039
+ lines.push(`Application: ${state.options.appKey || state.options.appSlug}`);
1040
+ }
1025
1041
  lines.push('');
1026
1042
  lines.push('Conversation transcript:');
1027
1043
  for (const message of state.messages) {
@@ -1,5 +1,7 @@
1
1
  export interface PulseAgentOptions {
2
2
  slug: string;
3
+ appKey?: string;
4
+ appSlug?: string;
3
5
  apiBaseUrl?: string;
4
6
  followUpEmailTo?: string;
5
7
  dashboardUrl?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"pulse-agent.d.ts","sourceRoot":"","sources":["../../src/widget/pulse-agent.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,KAAK,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,IAAI,IAAI,CAAC;IACb,KAAK,IAAI,IAAI,CAAC;IACd,OAAO,IAAI,IAAI,CAAC;IAChB,iBAAiB,IAAI,IAAI,CAAC;IAC1B,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG,IAAI,CAAC;IAClD,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACpE,mBAAmB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC5C;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAmCD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,gBAAgB,CAuP7E"}
1
+ {"version":3,"file":"pulse-agent.d.ts","sourceRoot":"","sources":["../../src/widget/pulse-agent.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,KAAK,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,IAAI,IAAI,CAAC;IACb,KAAK,IAAI,IAAI,CAAC;IACd,OAAO,IAAI,IAAI,CAAC;IAChB,iBAAiB,IAAI,IAAI,CAAC;IAC1B,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG,IAAI,CAAC;IAClD,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACpE,mBAAmB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC5C;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAsCD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,gBAAgB,CAuP7E"}