@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 +401 -53
- package/dist/browser/pulse-agent.global.js +25 -9
- package/dist/widget/pulse-agent.d.ts +2 -0
- package/dist/widget/pulse-agent.d.ts.map +1 -1
- package/dist/widget/pulse-agent.js +25 -9
- package/dist/widget/pulse-agent.js.map +1 -1
- package/docs/CHANGELOG.md +31 -0
- package/docs/FAQ.md +58 -0
- package/docs/IMPLEMENTATION-GUIDE.md +309 -0
- package/docs/INTEGRATION-CHANNELS.md +143 -0
- package/docs/TROUBLESHOOTING.md +86 -0
- package/package.json +37 -36
package/README.md
CHANGED
|
@@ -1,97 +1,445 @@
|
|
|
1
1
|
# @primexperts.co/pulse-agent
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
5
|
+
It is designed for teams that want one lightweight install to do two jobs:
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
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
|
-
|
|
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
|
|
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: '
|
|
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
|
-
##
|
|
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
|
-
|
|
41
|
-
slug: '
|
|
42
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
232
|
+
return () => agent.destroy();
|
|
233
|
+
}, []);
|
|
56
234
|
|
|
57
|
-
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
```
|
|
58
238
|
|
|
59
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
##
|
|
259
|
+
## How Widget Resolution Works
|
|
76
260
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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, '"')
|
|
949
962
|
.replace(/'/g, ''');
|
|
950
963
|
}
|
|
951
|
-
function getStorageKey(
|
|
952
|
-
return `pulse-agent-session:${
|
|
964
|
+
function getStorageKey(scopeKey) {
|
|
965
|
+
return `pulse-agent-session:${scopeKey.toLowerCase()}`;
|
|
953
966
|
}
|
|
954
|
-
function loadPersistedSession(
|
|
967
|
+
function loadPersistedSession(scopeKey) {
|
|
955
968
|
try {
|
|
956
969
|
if (typeof localStorage === 'undefined') {
|
|
957
970
|
return {};
|
|
958
971
|
}
|
|
959
|
-
const raw = localStorage.getItem(getStorageKey(
|
|
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
|
|
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 +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;
|
|
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"}
|