pager-widget 1.0.11-alpha.9 → 1.0.13

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.
Files changed (79) hide show
  1. package/dist/index.css +1 -1
  2. package/dist/index.html +40 -0
  3. package/dist/index.js +8 -8
  4. package/dist/react-parcel.9f367d21.css +2970 -0
  5. package/dist/react-parcel.9f367d21.css.map +1 -0
  6. package/dist/react-parcel.js +231527 -0
  7. package/dist/react-parcel.js.map +1 -0
  8. package/dist/server.html +33 -39
  9. package/out/AgentFeedbackButtonWrapper.js +15 -0
  10. package/out/App.js +28 -0
  11. package/out/BotMain.js +91 -0
  12. package/out/ButtonsWrapper.js +29 -0
  13. package/out/ChatPage.js +9 -0
  14. package/out/ChatPageContent.js +131 -0
  15. package/out/ChatPageHeader.js +267 -0
  16. package/out/ChatPageInput.js +29 -0
  17. package/out/ChatWidget.js +17 -0
  18. package/out/Consent.js +66 -0
  19. package/out/CountryDropdown.js +31 -0
  20. package/out/Form.js +238 -0
  21. package/out/HomePage.js +11 -0
  22. package/out/HomePageContent.js +32 -0
  23. package/out/HomePageFooter.js +87 -0
  24. package/out/HomePageHeader.js +66 -0
  25. package/out/InputField.js +80 -0
  26. package/out/Markdown.js +126 -0
  27. package/out/Message.js +197 -0
  28. package/out/MessagesT.js +2 -0
  29. package/out/NewRecentMessagePage.js +525 -0
  30. package/out/NewRecentMessageTab.js +23 -0
  31. package/out/ReceivedMessage.js +211 -0
  32. package/out/RecentMessageDetail.js +178 -0
  33. package/out/RecentMessageList.js +112 -0
  34. package/out/RecentMessageListCard.js +20 -0
  35. package/out/RecentMessageListHeader.js +156 -0
  36. package/out/RecentMessagePage.js +183 -0
  37. package/out/RecentMessageTab.js +35 -0
  38. package/out/WidgetMessage.js +14 -0
  39. package/out/WidgetToggleButton.js +191 -0
  40. package/out/WidgetType.js +2 -0
  41. package/out/assets/icons/pikaicons-react.js +17475 -0
  42. package/out/components/ConsentForm.js +7 -0
  43. package/out/components/CustomTooltip.js +34 -0
  44. package/out/hooks/useChatHistoryExists.js +32 -0
  45. package/out/hooks/useChatHistoryListExist.js +81 -0
  46. package/out/hooks/useChatScroll.js +14 -0
  47. package/out/hooks/useCurrentConversationExists.js +88 -0
  48. package/out/hooks/useInitialMessage.js +33 -0
  49. package/out/hooks/useMessage.js +230 -0
  50. package/out/hooks/useMessageReceiver.js +183 -0
  51. package/out/hooks/usePopupAnimation.js +20 -0
  52. package/out/hooks/useSocket.js +19 -0
  53. package/out/hooks/useSocketContext.js +81 -0
  54. package/out/hooks/useWidgetDimension.js +41 -0
  55. package/out/hooks/useWidgetToggle.js +22 -0
  56. package/out/index.js +31 -0
  57. package/out/markdown/CodeCopy.js +22 -0
  58. package/out/markdown/CodeHighlight.js +58 -0
  59. package/out/markdown/PagerMarkdown.js +36 -0
  60. package/out/markdown/pager_md.js +152 -0
  61. package/out/onscreenNotificationPopup.js +23 -0
  62. package/out/socket.js +11 -0
  63. package/out/utils/checkConversationExit.js +24 -0
  64. package/out/utils/convertTime.js +43 -0
  65. package/out/utils/sendInitialMessage.js +82 -0
  66. package/out/widget.js +21 -0
  67. package/out/widgetConfigStore.js +205 -0
  68. package/out/widgetConfigStore2.js +569 -0
  69. package/out/widgetControlstore.js +23 -0
  70. package/out/widgetStateHandler.js +106 -0
  71. package/out/widgetStore.js +171 -0
  72. package/out/widgetUserStore.js +47 -0
  73. package/package.json +3 -3
  74. package/server.html +33 -39
  75. package/.husky/pre-commit +0 -4
  76. package/docs/issues/001-use-widget-resize-hook.md +0 -51
  77. package/docs/issues/002-embed-snippet-container-resize.md +0 -81
  78. package/docs/prd/0001-iframe-resize-scroll-through.md +0 -333
  79. package/test-iframe-resize.html +0 -74
@@ -1,333 +0,0 @@
1
- # PRD: iframe Container Resize for Page Scroll-Through
2
-
3
- ---
4
-
5
- ## Problem Statement
6
-
7
- The Pager Widget is embedded inside an `<iframe>` to bypass SharePoint's Content-Security-Policy. The iframe is styled as `position: fixed; width: 100%; height: 100%; bottom: 0; right: 0; z-index: 9999` — covering the entire viewport.
8
-
9
- This creates a critical UX bug:
10
-
11
- - The iframe captures **all** pointer events on the page, even though the visible widget occupies only a small area (a launcher button when collapsed, or a chat panel when open).
12
- - Users **cannot scroll** the host page.
13
- - Users **cannot click** links, buttons, or any other interactive elements on the page behind the iframe.
14
- - The page is effectively frozen except for interaction with the widget itself.
15
-
16
- The widget is supposed to be a floating assistant, not a page takeover.
17
-
18
- ---
19
-
20
- ## Architecture (Two-Tier Iframe Model)
21
-
22
- Replace the full-viewport iframe with a two-tier iframe pattern:
23
-
24
- ```
25
- Customer's SharePoint page
26
-
27
- └── <iframe src="https://.../embed/{widget_id}"> ← outer iframe (small, sized to content)
28
-
29
- └── server.html (CONTAINER PAGE, served by backend)
30
-
31
- ├── div#pager-widget-container
32
- │ position:fixed; bottom:right; min 60×60px
33
- │ dynamically resized via postMessage
34
- │ │
35
- │ └── <iframe src="https://.../wgt-hook/embed/..."> ← inner iframe
36
- │ │
37
- │ └── Widget React App
38
- │ └── useWidgetResize (ResizeObserver → postMessage)
39
-
40
- └── Message listener: resizes div#pager-widget-container
41
- ```
42
-
43
- The outer iframe is sized by SharePoint's web part to match however the
44
- back end decides to size it. The container page (`server.html`) contains
45
- a fixed-position container div that starts at launcher size and is
46
- dynamically resized when the widget reports its actual dimensions.
47
-
48
- **Result:**
49
- - The outer iframe is small (matches widget's visible area) → 99% of the SharePoint page is free for scrolling/clicks.
50
- - When the widget is **collapsed** → container is ~60×60px → only the launcher is visible.
51
- - When the widget is **open** → container is ~448×637px → only the chat panel area captures events.
52
- - The page is never blocked from scrolling or clicking outside the widget's visible area.
53
-
54
- ---
55
-
56
- ## User Stories
57
-
58
- 1. As a page visitor, I want to scroll the content behind the widget launcher or chat panel, so that I can read long articles, forms, or product pages without the widget blocking me.
59
-
60
- 2. As a page visitor, I want to click on links, buttons, and form fields on the page while the widget is present (even when open in a corner), so that I can interact with the page normally.
61
-
62
- 3. As a page visitor, I want to click the widget's launcher button to open the chat, so that I can talk to the AI assistant when I choose to.
63
-
64
- 4. As a page visitor, I want to click outside the chat panel (or hit close) to collapse the widget and return to full page interaction, so that I'm not stuck in the chat.
65
-
66
- 5. As a developer, I want the widget to tell the parent page its current dimensions, so that the iframe container can be resized precisely to match what the user sees.
67
-
68
- 6. As a developer, I want the resize logic to be in one place (the toggle handler), so that it's simple to maintain and debug.
69
-
70
- 7. As a developer, I want the embed snippet to work for any customer without requiring them to write custom JavaScript, so that adoption is seamless.
71
-
72
- ---
73
-
74
- ## Implementation Decisions
75
-
76
- ### Module 1: Widget Frontend — ResizeObserver-based Hook
77
-
78
- **File**: `src/hooks/useWidgetResize.ts`
79
-
80
- **Purpose**: Send the widget's current rendered dimensions to the parent page whenever the widget's DOM size changes — toggle, expand, window resize, or any layout shift.
81
-
82
- **Behavior**:
83
-
84
- ```typescript
85
- // src/hooks/useWidgetResize.ts
86
- import { useEffect } from 'react'
87
-
88
- export function useWidgetResize() {
89
- useEffect(() => {
90
- const el = document.querySelector('.pager-widget-root')
91
- if (!el || window.parent === window) return
92
-
93
- let rafId: number | null = null
94
- const observer = new ResizeObserver(([entry]) => {
95
- if (rafId) return // throttle to one rAF at a time
96
- rafId = requestAnimationFrame(() => {
97
- rafId = null
98
- const { width, height } = entry.contentRect
99
- if (width === 0 || height === 0) return
100
- window.parent.postMessage(
101
- {
102
- type: 'widgetResize',
103
- width: Math.ceil(width),
104
- height: Math.ceil(height),
105
- },
106
- '*'
107
- )
108
- })
109
- })
110
- observer.observe(el)
111
-
112
- // Fire initial size on mount
113
- requestAnimationFrame(() => {
114
- const rect = el.getBoundingClientRect()
115
- if (rect.width > 0 && rect.height > 0) {
116
- window.parent.postMessage(
117
- {
118
- type: 'widgetResize',
119
- width: Math.ceil(rect.width),
120
- height: Math.ceil(rect.height),
121
- },
122
- '*'
123
- )
124
- }
125
- })
126
-
127
- return () => {
128
- observer.disconnect()
129
- if (rafId) cancelAnimationFrame(rafId)
130
- }
131
- }, [])
132
- }
133
- ```
134
-
135
- **Integration point**: Call this hook in `widget.tsx` where the toggle state lives, or in `App.tsx`:
136
-
137
- ```typescript
138
- // src/widget.tsx
139
- export const Widget = (widgetConfig: WidgetConfigProps) => {
140
- const [chatWindowStatus, toggleChatWindow] = useWidgetToggle(...)
141
- useWidgetResize()
142
- // ...
143
- }
144
- ```
145
-
146
- **Why ResizeObserver instead of state-based**:
147
- - Catches ALL size changes (toggle, expand `DEFAULT`/`EXPANDED`/`FULLSCREEN`, window resize, content growth).
148
- - No need to track which state variable caused the resize — the DOM is the source of truth.
149
- - Reuses the same ResizeObserver pattern already used in `BotMain.tsx` (lines 70-81).
150
- - Eliminates the `setTimeout(50ms)` race condition — the observer fires after layout.
151
- - `requestAnimationFrame` throttling prevents flooding while staying responsive.
152
-
153
- **Edge cases handled**:
154
- - **Not in iframe**: Skip entirely — `window.parent === window`.
155
- - **Zero dimensions**: Skip if measured width or height is 0 (DOM not ready).
156
- - **Initial render**: Fires once on mount after the first rAF.
157
- - **Flood protection**: One message per animation frame via `rafId` guard.
158
-
159
- ### Module 2: Backend Container Page — `server.html`
160
-
161
- **File**: `server.html` (updated)
162
-
163
- The backend serves a container page at `/embed/{widget_id}` that wraps the widget
164
- in a resizable container using HubSpot's pattern. Customers embed this page as an
165
- outer iframe on their SharePoint site.
166
-
167
- **Updated `server.html`**:
168
-
169
- ```html
170
- <!doctype html>
171
- <html lang="en">
172
- <head>
173
- <meta charset="UTF-8" />
174
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
175
- <title>Pager Widget</title>
176
- <style>
177
- body { margin: 0; overflow: hidden; }
178
- </style>
179
- </head>
180
- <body>
181
- <div id="pager-widget-container"
182
- style="position:fixed;bottom:0;right:0;z-index:9999;min-height:60px;min-width:60px">
183
- <iframe id="pager-widget-iframe"
184
- src="https://dev.pagergpt.ai/wgt-hook/embed/ae320291-260f-48fc-a069-68ab73858664?v=1.0.10&SYS_FirstName=John&SYS_UserEmail=john@example.com"
185
- style="position:absolute;width:100%!important;height:100%!important;border:none!important;bottom:0!important;right:0!important;background:transparent!important"
186
- title="Pager Widget">
187
- </iframe>
188
- </div>
189
- <script>
190
- (function() {
191
- var container = document.getElementById('pager-widget-container');
192
- var iframe = document.getElementById('pager-widget-iframe');
193
-
194
- // Listen for resize messages from the widget
195
- window.addEventListener('message', function(event) {
196
- if (event.data && event.data.type === 'widgetResize') {
197
- container.style.width = event.data.width + 'px';
198
- container.style.height = event.data.height + 'px';
199
- }
200
- });
201
-
202
- // Send user details to the inner widget iframe
203
- iframe.addEventListener('load', function() {
204
- iframe.contentWindow.postMessage({
205
- type: 'userDetails',
206
- data: {
207
- SYS_FirstName: "{{firstName}}",
208
- SYS_LastName: "{{lastName}}",
209
- SYS_UserEmail: "{{email}}",
210
- SYS_Location: "{{location}}",
211
- SYS_Timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
212
- }
213
- }, 'https://dev.pagergpt.ai');
214
- });
215
- })();
216
- </script>
217
- </body>
218
- </html>
219
- ```
220
-
221
- **Container CSS breakdown**:
222
-
223
- | Property | Value | Purpose |
224
- |---|---|---|
225
- | `position: fixed` | bottom; right | Anchors widget to viewport corner, scrolls with page |
226
- | `z-index: 9999` | high value | Stays above page content |
227
- | `min-height: 60px` | launcher button height | Prevents collapse to zero before first message |
228
- | `min-width: 60px` | launcher button width | Prevents collapse to zero before first message |
229
- | `width` / `height` | set dynamically | Updated by message listener |
230
-
231
- **Inner iframe CSS breakdown**:
232
-
233
- | Property | Value | Purpose |
234
- |---|---|---|
235
- | `position: absolute` | — | Fills the container, not the viewport |
236
- | `width: 100% !important` | — | Matches container width |
237
- | `height: 100% !important` | — | Matches container height |
238
- | `border: none !important` | — | No visible border |
239
- | `background: transparent !important` | — | No white flash behind the widget |
240
-
241
- **Security**: The `postMessage` listener does not check `event.origin` since different customers have different SharePoint domains. The widget side uses `targetOrigin: '*'`. This can be hardened later with a known-allowlist of customer origins.
242
-
243
- ### Module 3: Backend iframe HTML Template
244
-
245
- **File**: `src/public/public.router.ts` (existing — no changes to the router)
246
-
247
- The backend route `GET /embed/:widget_id` continues to serve `server.html`.
248
- The template variables (`{{firstName}}`, `{{email}}`, etc.) are filled in by
249
- the backend. No router changes are needed — only the template file itself
250
- is updated (Module 2).
251
-
252
- ---
253
-
254
- ## Testing Decisions
255
-
256
- ### What makes a good test
257
-
258
- - Test the **contract**: when the widget toggles state, a `postMessage` with the correct shape is dispatched.
259
- - Test **edge cases**: not in iframe (skip), zero dimensions (skip), DOM not ready (skip).
260
- - Do NOT test the parent page's resize behavior — that requires a browser/DOM environment and is covered by manual QA.
261
-
262
- ### Tests for Module 1 (useWidgetResize hook)
263
-
264
- **File**: `src/__tests__/useWidgetResize.test.ts`
265
-
266
- | Test | Input | Expected |
267
- |---|---|---|
268
- | ResizeObserver fires on size change | Mock ResizeObserver with 400×600 rect | `postMessage` called with `{ type: 'widgetResize', width: 400, height: 600 }` |
269
- | Skip if not in iframe | `window.parent === window` | `postMessage` NOT called |
270
- | Skip if dimensions are zero | Mock ResizeObserver with 0×0 rect | `postMessage` NOT called |
271
- | Initial mount fires one message | Simulate mount + rAF | `postMessage` called with actual dimensions |
272
- | Cleanup on unmount | Disconnect observer + cancel rAF | Observer.disconnect() called |
273
-
274
- ### Prior art
275
-
276
- No existing tests in `poc-pager-widget` — this is the first. `vitest` must be installed as a devDependency along with `jsdom` environment.
277
-
278
- ```bash
279
- npm install -D vitest jsdom
280
- ```
281
-
282
- ---
283
-
284
- ## Integration Points
285
-
286
- ### Where to connect
287
-
288
- ```
289
- server.html (served at /embed/{widget_id})
290
-
291
- ├── Container div (fixed, bottom-right, min 60×60)
292
- │ └── Inner iframe → Widget React App
293
- │ │
294
- │ ├── widget.tsx
295
- │ │ ├── useWidgetToggle() ← existing
296
- │ │ ├── useWidgetResize() ← NEW (ResizeObserver)
297
- │ │ └── <WidgetToggleButton ... />
298
- │ │
299
- │ └── ResizeObserver fires → postMessage({ type: 'widgetResize', width, height })
300
-
301
- └── Message listener receives postMessage
302
- └── container.style.width = ...px
303
- └── container.style.height = ...px
304
- ```
305
-
306
- ### Where is the toggle state?
307
-
308
- The widget already has a way to toggle open/collapsed. Find the existing open/close state — likely one of:
309
- - `widgetStore.ts` — the main xstate store
310
- - `widgetControlstore.ts` — widget control state
311
- - `WidgetToggleButton.tsx` — the toggle button component
312
-
313
- The hook needs to read this state. The exact store/state key needs to be identified during implementation.
314
-
315
- ---
316
-
317
- ## Out of Scope
318
-
319
- - **Animation or transitions**: Container resize is instant. CSS `transition` on the container can be added later.
320
- - **Responsive dimensions**: The widget does not need to send resize events on window resize — ResizeObserver handles this automatically.
321
- - **Multiple widget instances**: Only one widget per page is supported.
322
- - **Mobile considerations**: The same pattern works on mobile. The dimensions sent will naturally be smaller due to viewport size. No special mobile handling needed.
323
- - **origin validation**: The parent page listens unconditionally. If needed later, the widget can harden `targetOrigin` or validate `event.origin`.
324
-
325
- ---
326
-
327
- ## Further Notes
328
-
329
- - HubSpot's own widget uses exactly the container-resize pattern. Their container starts at minimum size and gets resized dynamically. This is a proven pattern used at scale.
330
- - The initial `min-height: 60px; min-width: 60px` matches the approximate launcher button size. When the widget first loads and sends its first `widgetResize` message, the container is resized to the actual rendered dimensions.
331
- - If the widget fails to send the initial resize message (e.g., JS error), the container stays at 60×60px — users will see a tiny square but the page remains fully functional. This is a safe failure mode.
332
- - The ResizeObserver fires for ANY size change: toggle open/close, expand (DEFAULT/EXPANDED/FULLSCREEN), window resize, or content growth. This is more robust than state-based triggers.
333
- - The widget URL (`https://dev.pagergpt.ai/wgt-hook/embed/{widget_id}?v={version}`) that the inner iframe loads is unaffected by this change — only `server.html` (the outer container page) is updated.
@@ -1,74 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Iframe Resize Test</title>
7
- <style>
8
- body { margin: 0; min-height: 200vh; background: #f5f5f5; font-family: sans-serif; }
9
- .page-content { padding: 40px; max-width: 600px; }
10
- h1 { color: #333; }
11
- p { color: #666; line-height: 1.6; margin-bottom: 20px; }
12
- .debug-panel {
13
- position: fixed; top: 10px; left: 10px; z-index: 99999;
14
- background: rgba(0,0,0,0.8); color: #0f0; padding: 10px 14px;
15
- border-radius: 8px; font-size: 13px; font-family: monospace;
16
- max-width: 300px;
17
- }
18
- .debug-panel div { margin: 2px 0; }
19
- </style>
20
- </head>
21
- <body>
22
- <div class="page-content">
23
- <h1>Pager Widget — Iframe Resize Test</h1>
24
- <p>This page simulates the container pattern from <code>server.html</code>.</p>
25
- <p>The widget loads inside an iframe that is contained within a fixed-position
26
- container div. The container starts at 60×60px and gets resized dynamically
27
- when the widget sends <code>widgetResize</code> postMessages.</p>
28
- <p><strong>Test checklist:</strong></p>
29
- <ul>
30
- <li>Page should be scrollable at all times (not blocked by iframe)</li>
31
- <li>Widget launcher should be clickable</li>
32
- <li>Opening the widget should resize the container (watch debug panel)</li>
33
- <li>Clicking the X should collapse the widget and shrink the container</li>
34
- <li>Clicking expand buttons (EXPANDED / FULLSCREEN) should resize container</li>
35
- <li>Page content behind the widget should remain clickable</li>
36
- </ul>
37
- <p style="margin-top: 60vh;">↓ Scroll down (should work even with widget open)</p>
38
- <p style="margin-top: 40vh;">Bottom of page. Scrolling worked!</p>
39
- </div>
40
-
41
- <!-- Container div with iframe (mimics server.html pattern) -->
42
- <div id="pager-widget-container"
43
- style="position:fixed;bottom:0;right:0;z-index:9999;min-height:60px;min-width:60px">
44
- <iframe id="pager-widget-iframe"
45
- src="http://localhost:8000/embed/ae320291-260f-48fc-a069-68ab73858664?v=1.0.11-alpha.7&SYS_FirstName=John&SYS_UserEmail=john@example.com"
46
- style="position:absolute;width:100%;height:100%;border:none;background:transparent"
47
- title="Pager Widget">
48
- </iframe>
49
- </div>
50
-
51
- <script>
52
- (function() {
53
- var container = document.getElementById('pager-widget-container');
54
-
55
- // Debug panel
56
- var debug = document.createElement('div');
57
- debug.className = 'debug-panel';
58
- debug.innerHTML = '<div><strong>Resize Debug</strong></div><div id="debug-size">waiting...</div>';
59
- document.body.appendChild(debug);
60
- var debugSize = document.getElementById('debug-size');
61
-
62
- window.addEventListener('message', function(event) {
63
- console.log(event.data, "from inside iframe");
64
- if (event.data && event.data.type === 'widgetResize') {
65
- container.style.width = (Number(event.data.width) + 100) + 'px';
66
- container.style.height = (Number(event.data.height) + 200) + 'px';
67
- debugSize.textContent = event.data.width + ' x ' + event.data.height;
68
- console.log('[widgetResize]', event.data.width, 'x', event.data.height);
69
- }
70
- });
71
- })();
72
- </script>
73
- </body>
74
- </html>