@memberjunction/realtime-widget 0.0.0 → 5.45.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.
Files changed (86) hide show
  1. package/README.md +107 -43
  2. package/dist/embed.d.ts +18 -0
  3. package/dist/embed.d.ts.map +1 -0
  4. package/dist/embed.js +33 -0
  5. package/dist/embed.js.map +1 -0
  6. package/dist/index.d.ts +23 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +37 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/loader.d.ts +32 -0
  11. package/dist/loader.d.ts.map +1 -0
  12. package/dist/loader.js +163 -0
  13. package/dist/loader.js.map +1 -0
  14. package/dist/session/visitor-key-cookie.d.ts +20 -0
  15. package/dist/session/visitor-key-cookie.d.ts.map +1 -0
  16. package/dist/session/visitor-key-cookie.js +60 -0
  17. package/dist/session/visitor-key-cookie.js.map +1 -0
  18. package/dist/session/widget-session-client.d.ts +74 -0
  19. package/dist/session/widget-session-client.d.ts.map +1 -0
  20. package/dist/session/widget-session-client.js +147 -0
  21. package/dist/session/widget-session-client.js.map +1 -0
  22. package/dist/transport/mock-widget-transport.d.ts +26 -0
  23. package/dist/transport/mock-widget-transport.d.ts.map +1 -0
  24. package/dist/transport/mock-widget-transport.js +42 -0
  25. package/dist/transport/mock-widget-transport.js.map +1 -0
  26. package/dist/transport/runtime-widget-transport.d.ts +54 -0
  27. package/dist/transport/runtime-widget-transport.d.ts.map +1 -0
  28. package/dist/transport/runtime-widget-transport.js +203 -0
  29. package/dist/transport/runtime-widget-transport.js.map +1 -0
  30. package/dist/transport/widget-transport.d.ts +41 -0
  31. package/dist/transport/widget-transport.d.ts.map +1 -0
  32. package/dist/transport/widget-transport.js +13 -0
  33. package/dist/transport/widget-transport.js.map +1 -0
  34. package/dist/types.d.ts +104 -0
  35. package/dist/types.d.ts.map +1 -0
  36. package/dist/types.js +6 -0
  37. package/dist/types.js.map +1 -0
  38. package/dist/ui/browser-capabilities.d.ts +36 -0
  39. package/dist/ui/browser-capabilities.d.ts.map +1 -0
  40. package/dist/ui/browser-capabilities.js +30 -0
  41. package/dist/ui/browser-capabilities.js.map +1 -0
  42. package/dist/ui/support-widget-element.d.ts +149 -0
  43. package/dist/ui/support-widget-element.d.ts.map +1 -0
  44. package/dist/ui/support-widget-element.js +540 -0
  45. package/dist/ui/support-widget-element.js.map +1 -0
  46. package/dist/ui/tokens.d.ts +16 -0
  47. package/dist/ui/tokens.d.ts.map +1 -0
  48. package/dist/ui/tokens.js +135 -0
  49. package/dist/ui/tokens.js.map +1 -0
  50. package/dist/voice/channels/base-widget-channel.d.ts +63 -0
  51. package/dist/voice/channels/base-widget-channel.d.ts.map +1 -0
  52. package/dist/voice/channels/base-widget-channel.js +36 -0
  53. package/dist/voice/channels/base-widget-channel.js.map +1 -0
  54. package/dist/voice/channels/whiteboard-channel.d.ts +38 -0
  55. package/dist/voice/channels/whiteboard-channel.d.ts.map +1 -0
  56. package/dist/voice/channels/whiteboard-channel.js +214 -0
  57. package/dist/voice/channels/whiteboard-channel.js.map +1 -0
  58. package/dist/voice/channels/widget-channel-host.d.ts +68 -0
  59. package/dist/voice/channels/widget-channel-host.d.ts.map +1 -0
  60. package/dist/voice/channels/widget-channel-host.js +111 -0
  61. package/dist/voice/channels/widget-channel-host.js.map +1 -0
  62. package/dist/voice/guest-tool-relay.d.ts +14 -0
  63. package/dist/voice/guest-tool-relay.d.ts.map +1 -0
  64. package/dist/voice/guest-tool-relay.js +28 -0
  65. package/dist/voice/guest-tool-relay.js.map +1 -0
  66. package/dist/voice/guest-voice-mint.d.ts +23 -0
  67. package/dist/voice/guest-voice-mint.d.ts.map +1 -0
  68. package/dist/voice/guest-voice-mint.js +61 -0
  69. package/dist/voice/guest-voice-mint.js.map +1 -0
  70. package/dist/voice/mock-voice-controller.d.ts +22 -0
  71. package/dist/voice/mock-voice-controller.d.ts.map +1 -0
  72. package/dist/voice/mock-voice-controller.js +42 -0
  73. package/dist/voice/mock-voice-controller.js.map +1 -0
  74. package/dist/voice/realtime-voice-controller.d.ts +75 -0
  75. package/dist/voice/realtime-voice-controller.d.ts.map +1 -0
  76. package/dist/voice/realtime-voice-controller.js +182 -0
  77. package/dist/voice/realtime-voice-controller.js.map +1 -0
  78. package/dist/voice/voice-abuse-guard.d.ts +39 -0
  79. package/dist/voice/voice-abuse-guard.d.ts.map +1 -0
  80. package/dist/voice/voice-abuse-guard.js +57 -0
  81. package/dist/voice/voice-abuse-guard.js.map +1 -0
  82. package/dist/voice/voice-controller.d.ts +39 -0
  83. package/dist/voice/voice-controller.d.ts.map +1 -0
  84. package/dist/voice/voice-controller.js +10 -0
  85. package/dist/voice/voice-controller.js.map +1 -0
  86. package/package.json +36 -7
package/README.md CHANGED
@@ -1,45 +1,109 @@
1
1
  # @memberjunction/realtime-widget
2
2
 
3
- ## ⚠️ IMPORTANT NOTICE ⚠️
4
-
5
- **This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
6
-
7
- This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
8
-
9
- ## Purpose
10
-
11
- This package exists to:
12
- 1. Configure OIDC trusted publishing for the package name `@memberjunction/realtime-widget`
13
- 2. Enable secure, token-less publishing from CI/CD workflows
14
- 3. Establish provenance for packages published under this name
15
-
16
- ## What is OIDC Trusted Publishing?
17
-
18
- OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
19
-
20
- ## Setup Instructions
21
-
22
- To properly configure OIDC trusted publishing for this package:
23
-
24
- 1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
25
- 2. Configure the trusted publisher (e.g., GitHub Actions)
26
- 3. Specify the repository and workflow that should be allowed to publish
27
- 4. Use the configured workflow to publish your actual package
28
-
29
- ## DO NOT USE THIS PACKAGE
30
-
31
- This package is a placeholder for OIDC configuration only. It:
32
- - Contains no executable code
33
- - Provides no functionality
34
- - Should not be installed as a dependency
35
- - Exists only for administrative purposes
36
-
37
- ## More Information
38
-
39
- For more details about npm's trusted publishing feature, see:
40
- - [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
41
- - [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
42
-
43
- ---
44
-
45
- **Maintained for OIDC setup purposes only**
3
+ A **droppable, embeddable public customer-support widget** (text today; voice in W4) for any
4
+ third-party website. A single `<script>` tag plus one mount element opens a support
5
+ conversation with a **pinned MJ agent** **no MJ login required** by minting a short-lived
6
+ **anonymous guest session** and reusing MemberJunction's unified agent pathway
7
+ (`ConversationsRuntime` + the GraphQL data provider). It does **not** reimplement chat or agent
8
+ dispatch.
9
+
10
+ Part of the [Agent Bridges & Public Widget program](../../../plans/realtime/bridges-and-widget/README.md)
11
+ (phases **W3+**). See [`public-web-widget.md`](../../../plans/realtime/bridges-and-widget/public-web-widget.md).
12
+
13
+ ## Embedding (the entire integration)
14
+
15
+ ```html
16
+ <div
17
+ data-widget-key="pk_live_xxx"
18
+ data-api-url="https://api.yourco.com"
19
+ data-title="Acme Support"
20
+ data-greeting="Hi! How can we help?"
21
+ ></div>
22
+ <script type="module" src="https://cdn.yourco.com/mj-widget.js"></script>
23
+ ```
24
+
25
+ The loader reads the `data-*` attributes, calls `POST /widget/session` to mint a guest JWT, and
26
+ mounts `<mj-support-widget>` **inside a shadow DOM** so host CSS cannot bleed in or out. See
27
+ [`examples/blank-host.html`](./examples/blank-host.html).
28
+
29
+ ### Programmatic mount
30
+
31
+ ```ts
32
+ import { mountWidget } from '@memberjunction/realtime-widget';
33
+ await mountWidget({ widgetKey: 'pk_live_xxx', apiUrl: 'https://api.yourco.com', mountTarget: '#support' });
34
+ ```
35
+
36
+ ## Content Security Policy (host sites)
37
+
38
+ If your site sends a `Content-Security-Policy` header (it should), the widget needs four allowances:
39
+ the script bundle, the API origin (GraphQL over HTTPS **and** the realtime WebSocket for voice), and —
40
+ because the UI renders inside a shadow DOM with inlined token styles — `style-src 'unsafe-inline'`.
41
+ Replace `https://api.yourco.com` with your MJAPI origin and `https://cdn.yourco.com` with wherever you
42
+ host `mj-widget.js`:
43
+
44
+ ```
45
+ Content-Security-Policy:
46
+ script-src 'self' https://cdn.yourco.com;
47
+ connect-src 'self' https://api.yourco.com wss://api.yourco.com;
48
+ style-src 'self' 'unsafe-inline';
49
+ img-src 'self' data:;
50
+ frame-ancestors 'self';
51
+ ```
52
+
53
+ Notes:
54
+ - **`connect-src`** must include BOTH `https://` (the GraphQL mint + agent calls) and `wss://` (the
55
+ realtime voice socket). Voice silently fails to connect if the `wss:` origin is omitted.
56
+ - **`style-src 'unsafe-inline'`** is required because the shadow-DOM component injects its
57
+ design-token stylesheet inline. The shadow root keeps it isolated from your page's styles; it does
58
+ **not** widen your page's own CSP exposure to inline `<style>` you didn't author.
59
+ - **`img-src data:`** covers the inline SVG/emoji affordances.
60
+ - Voice also needs the browser mic permission; on a strict `Permissions-Policy`, allow
61
+ `microphone=(self "https://cdn.yourco.com")` (or your embed origin) or the mic toggle is hidden and
62
+ the widget falls back to text.
63
+
64
+ ## Architecture
65
+
66
+ ```
67
+ host page → <mj-support-widget> (shadow DOM, design-token-scoped)
68
+ ├─ WidgetSessionClient → POST /widget/session (guest JWT, refreshed before expiry)
69
+ └─ IWidgetTransport
70
+ ├─ RuntimeWidgetTransport (prod): setupGraphQLClient(guest token) + ConversationsRuntime
71
+ │ → AgentRunner.processMessage({ explicitAgentId: <pinned> }) ← D5
72
+ └─ MockWidgetTransport (tests / offline demo)
73
+ ```
74
+
75
+ - **Auth (D1, pluggable):** the guest session is an `anonymous-embed` magic-link token minted by
76
+ MJServer's `WidgetSessionService`, validated by the same `AuthProviderFactory` path. Strategies:
77
+ `Anonymous` (default), `MagicLinkUpgrade`, `HostIdentity` — selected per widget instance (W5).
78
+ - **Guardrails (D5):** the widget **always** passes the per-instance pinned `explicitAgentId`, and
79
+ the synthesized guest principal carries the restricted **Widget Guest** role (read/create/update
80
+ only on Conversations + Conversation Details). Pinning alone is not enough — see the
81
+ [W0 findings](../../../plans/realtime/bridges-and-widget/spikes/W0-findings.md).
82
+ - **Isolation:** shadow DOM + `all: initial` on `:host`; `--mj-chat-*` tokens are injected into the
83
+ **shadow root**, never `<head>`.
84
+
85
+ ## Scripts
86
+
87
+ ```bash
88
+ npm run build # tsc → dist/ (ESM)
89
+ npm run bundle # esbuild → dist/mj-widget.js (single-file browser ESM)
90
+ npm run bundle:min # minified variant
91
+ npm run test # vitest (jsdom): session client, element, loader
92
+ ```
93
+
94
+ ## Build & bundle status (overnight session)
95
+
96
+ - `npm run build` ✅ and `npm run test` ✅ (15 tests: mint, shadow-DOM isolation, send/receive,
97
+ pinned-agent pass-through, refresh math, loader mount).
98
+ - `npm run bundle` ✅ produces a **self-contained** browser ESM (no unresolved node built-ins).
99
+ It is currently large (~2.9 MB minified) because it bundles the full runtime + GraphQL provider;
100
+ **tree-shaking / code-splitting the bundle is W6 packaging hardening**, tracked, not done here.
101
+ - **Live end-to-end** (`blank-host.html` against a running MJAPI completing a real turn) requires
102
+ MJAPI booted with `widget.enabled=true` — **Auth0-gated** in the overnight sandbox. The code path
103
+ is complete and ready; it was not faked.
104
+
105
+ ## Not built here (anti-drift)
106
+
107
+ No new chat engine, no new realtime/voice driver, no new auth subsystem, no second agent-dispatch
108
+ path — all reused. Voice (W4), magic-link upgrade + host identity (W5), and bundle/abuse hardening
109
+ (W6) build on this package.
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @fileoverview Browser EMBED entry — the bundle the host page loads via `<script src="mj-widget.js">`.
3
+ *
4
+ * Deliberately TINY and free of static value-imports of the heavy modules (the runtime transport and
5
+ * the realtime voice stack). It only defines the custom element and auto-bootstraps from the host's
6
+ * `[data-widget-key]` element; the loader then DYNAMICALLY imports the transport (on mount) and the
7
+ * voice controller (only for Voice/Both widgets). Built with `esbuild --splitting`, this keeps
8
+ * `@memberjunction/graphql-dataprovider` + `@memberjunction/conversations-runtime` and
9
+ * `@memberjunction/ai-realtime-client` in separate chunks that load on demand — so the launcher
10
+ * paints fast and a text-only widget never downloads the voice chunk.
11
+ *
12
+ * The npm public API (with static exports for programmatic consumers) remains `index.ts`; this module
13
+ * is purely the self-running browser bundle entry.
14
+ *
15
+ * @module @memberjunction/realtime-widget
16
+ */
17
+ export {};
18
+ //# sourceMappingURL=embed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embed.d.ts","sourceRoot":"","sources":["../src/embed.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG"}
package/dist/embed.js ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @fileoverview Browser EMBED entry — the bundle the host page loads via `<script src="mj-widget.js">`.
3
+ *
4
+ * Deliberately TINY and free of static value-imports of the heavy modules (the runtime transport and
5
+ * the realtime voice stack). It only defines the custom element and auto-bootstraps from the host's
6
+ * `[data-widget-key]` element; the loader then DYNAMICALLY imports the transport (on mount) and the
7
+ * voice controller (only for Voice/Both widgets). Built with `esbuild --splitting`, this keeps
8
+ * `@memberjunction/graphql-dataprovider` + `@memberjunction/conversations-runtime` and
9
+ * `@memberjunction/ai-realtime-client` in separate chunks that load on demand — so the launcher
10
+ * paints fast and a text-only widget never downloads the voice chunk.
11
+ *
12
+ * The npm public API (with static exports for programmatic consumers) remains `index.ts`; this module
13
+ * is purely the self-running browser bundle entry.
14
+ *
15
+ * @module @memberjunction/realtime-widget
16
+ */
17
+ import { defineSupportWidgetElement } from './ui/support-widget-element.js';
18
+ import { bootstrapFromDocument } from './loader.js';
19
+ // When loaded in a browser, register the element and attempt auto-bootstrap from the host's
20
+ // [data-widget-key] element. No-ops cleanly server-side or when nothing matches.
21
+ if (typeof window !== 'undefined' && typeof document !== 'undefined') {
22
+ defineSupportWidgetElement();
23
+ const run = () => {
24
+ void bootstrapFromDocument();
25
+ };
26
+ if (document.readyState === 'loading') {
27
+ document.addEventListener('DOMContentLoaded', run, { once: true });
28
+ }
29
+ else {
30
+ run();
31
+ }
32
+ }
33
+ //# sourceMappingURL=embed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embed.js","sourceRoot":"","sources":["../src/embed.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,0BAA0B,EAAE,MAAM,gCAAgC,CAAC;AAC5E,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEpD,4FAA4F;AAC5F,iFAAiF;AACjF,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;IACnE,0BAA0B,EAAE,CAAC;IAC7B,MAAM,GAAG,GAAG,GAAS,EAAE;QACnB,KAAK,qBAAqB,EAAE,CAAC;IACjC,CAAC,CAAC;IACF,IAAI,QAAQ,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QACpC,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IACvE,CAAC;SAAM,CAAC;QACJ,GAAG,EAAE,CAAC;IACV,CAAC;AACL,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @fileoverview Public API for @memberjunction/realtime-widget + auto-bootstrap when loaded
3
+ * as a browser bundle. Only exports symbols defined in this package (no cross-package
4
+ * re-exports — CLAUDE rule 5).
5
+ *
6
+ * @module @memberjunction/realtime-widget
7
+ */
8
+ export * from './types.js';
9
+ export { WidgetSessionClient, type FetchLike } from './session/widget-session-client.js';
10
+ export type { IWidgetTransport, WidgetProgressCallback, WidgetTurnResult } from './transport/widget-transport.js';
11
+ export { MockWidgetTransport } from './transport/mock-widget-transport.js';
12
+ export { RuntimeWidgetTransport } from './transport/runtime-widget-transport.js';
13
+ export type { IVoiceController, VoiceControllerCallbacks, WidgetVoiceState, WidgetVoiceTranscript, } from './voice/voice-controller.js';
14
+ export { MockVoiceController } from './voice/mock-voice-controller.js';
15
+ export { RealtimeVoiceController, type VoiceMintFn, type VoiceMintResult, } from './voice/realtime-voice-controller.js';
16
+ export { VoiceAbuseGuard, DEFAULT_VOICE_LIMITS, type VoiceAbuseLimits, type VoiceAbortReason, } from './voice/voice-abuse-guard.js';
17
+ export { BaseWidgetChannel, type WidgetChannelToolDefinition, type WidgetChannelContext, } from './voice/channels/base-widget-channel.js';
18
+ export { WidgetChannelHost, type ChannelToolRouteResult } from './voice/channels/widget-channel-host.js';
19
+ export { WidgetWhiteboardChannel, LoadWidgetWhiteboardChannel } from './voice/channels/whiteboard-channel.js';
20
+ export { SupportWidgetElement, defineSupportWidgetElement, WIDGET_TAG_NAME } from './ui/support-widget-element.js';
21
+ export { WIDGET_SHADOW_STYLES } from './ui/tokens.js';
22
+ export { mountWidget, bootstrapFromDocument, type WidgetMountDeps } from './loader.js';
23
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,mBAAmB,EAAE,KAAK,SAAS,EAAE,MAAM,oCAAoC,CAAC;AACzF,YAAY,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAClH,OAAO,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;AAC3E,OAAO,EAAE,sBAAsB,EAAE,MAAM,yCAAyC,CAAC;AACjF,YAAY,EACR,gBAAgB,EAChB,wBAAwB,EACxB,gBAAgB,EAChB,qBAAqB,GACxB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EACH,uBAAuB,EACvB,KAAK,WAAW,EAChB,KAAK,eAAe,GACvB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EACH,eAAe,EACf,oBAAoB,EACpB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,GACxB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACH,iBAAiB,EACjB,KAAK,2BAA2B,EAChC,KAAK,oBAAoB,GAC5B,MAAM,yCAAyC,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,KAAK,sBAAsB,EAAE,MAAM,yCAAyC,CAAC;AACzG,OAAO,EAAE,uBAAuB,EAAE,2BAA2B,EAAE,MAAM,wCAAwC,CAAC;AAC9G,OAAO,EAAE,oBAAoB,EAAE,0BAA0B,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACnH,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,qBAAqB,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @fileoverview Public API for @memberjunction/realtime-widget + auto-bootstrap when loaded
3
+ * as a browser bundle. Only exports symbols defined in this package (no cross-package
4
+ * re-exports — CLAUDE rule 5).
5
+ *
6
+ * @module @memberjunction/realtime-widget
7
+ */
8
+ export * from './types.js';
9
+ export { WidgetSessionClient } from './session/widget-session-client.js';
10
+ export { MockWidgetTransport } from './transport/mock-widget-transport.js';
11
+ export { RuntimeWidgetTransport } from './transport/runtime-widget-transport.js';
12
+ export { MockVoiceController } from './voice/mock-voice-controller.js';
13
+ export { RealtimeVoiceController, } from './voice/realtime-voice-controller.js';
14
+ export { VoiceAbuseGuard, DEFAULT_VOICE_LIMITS, } from './voice/voice-abuse-guard.js';
15
+ export { BaseWidgetChannel, } from './voice/channels/base-widget-channel.js';
16
+ export { WidgetChannelHost } from './voice/channels/widget-channel-host.js';
17
+ export { WidgetWhiteboardChannel, LoadWidgetWhiteboardChannel } from './voice/channels/whiteboard-channel.js';
18
+ export { SupportWidgetElement, defineSupportWidgetElement, WIDGET_TAG_NAME } from './ui/support-widget-element.js';
19
+ export { WIDGET_SHADOW_STYLES } from './ui/tokens.js';
20
+ export { mountWidget, bootstrapFromDocument } from './loader.js';
21
+ import { defineSupportWidgetElement } from './ui/support-widget-element.js';
22
+ import { bootstrapFromDocument } from './loader.js';
23
+ // When loaded in a browser, register the element and attempt auto-bootstrap from the
24
+ // host's [data-widget-key] element. No-ops cleanly server-side or when nothing matches.
25
+ if (typeof window !== 'undefined' && typeof document !== 'undefined') {
26
+ defineSupportWidgetElement();
27
+ const run = () => {
28
+ void bootstrapFromDocument();
29
+ };
30
+ if (document.readyState === 'loading') {
31
+ document.addEventListener('DOMContentLoaded', run, { once: true });
32
+ }
33
+ else {
34
+ run();
35
+ }
36
+ }
37
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,mBAAmB,EAAkB,MAAM,oCAAoC,CAAC;AAEzF,OAAO,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;AAC3E,OAAO,EAAE,sBAAsB,EAAE,MAAM,yCAAyC,CAAC;AAOjF,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EACH,uBAAuB,GAG1B,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EACH,eAAe,EACf,oBAAoB,GAGvB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACH,iBAAiB,GAGpB,MAAM,yCAAyC,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAA+B,MAAM,yCAAyC,CAAC;AACzG,OAAO,EAAE,uBAAuB,EAAE,2BAA2B,EAAE,MAAM,wCAAwC,CAAC;AAC9G,OAAO,EAAE,oBAAoB,EAAE,0BAA0B,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACnH,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,qBAAqB,EAAwB,MAAM,aAAa,CAAC;AAEvF,OAAO,EAAE,0BAA0B,EAAE,MAAM,gCAAgC,CAAC;AAC5E,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEpD,qFAAqF;AACrF,wFAAwF;AACxF,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;IACnE,0BAA0B,EAAE,CAAC;IAC7B,MAAM,GAAG,GAAG,GAAS,EAAE;QACnB,KAAK,qBAAqB,EAAE,CAAC;IACjC,CAAC,CAAC;IACF,IAAI,QAAQ,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QACpC,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IACvE,CAAC;SAAM,CAAC;QACJ,GAAG,EAAE,CAAC;IACV,CAAC;AACL,CAAC"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @fileoverview The embed loader. Reads `data-widget-key` / `data-api-url` from the
3
+ * host page (the mount div or the script tag), mints a guest session, mounts the
4
+ * `<mj-support-widget>` element, registers the runtime notification adapter scoped to
5
+ * the widget, and schedules token refresh. This is the single entry point the
6
+ * `mj-widget.js` bundle auto-runs.
7
+ *
8
+ * @module @memberjunction/realtime-widget
9
+ */
10
+ import type { WidgetMountOptions, WidgetSession } from './types.js';
11
+ import { WidgetSessionClient } from './session/widget-session-client.js';
12
+ import type { IWidgetTransport } from './transport/widget-transport.js';
13
+ import type { IVoiceController } from './voice/voice-controller.js';
14
+ import { SupportWidgetElement } from './ui/support-widget-element.js';
15
+ /** Optional injection points so the loader is unit-testable without a network/runtime. */
16
+ export interface WidgetMountDeps {
17
+ /** Builds the session client (defaults to the real fetch-based one). */
18
+ sessionClientFactory?: (apiUrl: string, widgetKey: string) => WidgetSessionClient;
19
+ /** Builds the transport (defaults to RuntimeWidgetTransport; tests inject a mock). May be async (the default lazy-loads). */
20
+ transportFactory?: (apiUrl: string) => IWidgetTransport | Promise<IWidgetTransport>;
21
+ /** Builds the voice controller for voice-enabled instances (defaults to RealtimeVoiceController). May be async (the default lazy-loads). */
22
+ voiceControllerFactory?: (session: WidgetSession) => IVoiceController | Promise<IVoiceController>;
23
+ /** Schedules refresh (defaults to setTimeout); tests can stub. */
24
+ scheduler?: (cb: () => void, delayMs: number) => void;
25
+ }
26
+ /**
27
+ * Mints a guest session and mounts a configured widget element. Returns the element.
28
+ */
29
+ export declare function mountWidget(options: WidgetMountOptions, deps?: WidgetMountDeps): Promise<SupportWidgetElement>;
30
+ /** Reads data-attributes from the host and mounts. Safe to call once on script load. */
31
+ export declare function bootstrapFromDocument(doc?: Document): Promise<SupportWidgetElement | null>;
32
+ //# sourceMappingURL=loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AAEzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAEpE,OAAO,EAAE,oBAAoB,EAA+C,MAAM,gCAAgC,CAAC;AAEnH,0FAA0F;AAC1F,MAAM,WAAW,eAAe;IAC5B,wEAAwE;IACxE,oBAAoB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,mBAAmB,CAAC;IAClF,6HAA6H;IAC7H,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACpF,4IAA4I;IAC5I,sBAAsB,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAClG,kEAAkE;IAClE,SAAS,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACzD;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,kBAAkB,EAAE,IAAI,GAAE,eAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA2CxH;AAED,wFAAwF;AACxF,wBAAsB,qBAAqB,CAAC,GAAG,GAAE,QAAmB,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAe1G"}
package/dist/loader.js ADDED
@@ -0,0 +1,163 @@
1
+ /**
2
+ * @fileoverview The embed loader. Reads `data-widget-key` / `data-api-url` from the
3
+ * host page (the mount div or the script tag), mints a guest session, mounts the
4
+ * `<mj-support-widget>` element, registers the runtime notification adapter scoped to
5
+ * the widget, and schedules token refresh. This is the single entry point the
6
+ * `mj-widget.js` bundle auto-runs.
7
+ *
8
+ * @module @memberjunction/realtime-widget
9
+ */
10
+ import { WidgetSessionClient } from './session/widget-session-client.js';
11
+ import { readVisitorKey, writeVisitorKey, clearVisitorKey } from './session/visitor-key-cookie.js';
12
+ import { DEFAULT_VOICE_LIMITS } from './voice/voice-abuse-guard.js';
13
+ import { defineSupportWidgetElement, WIDGET_TAG_NAME } from './ui/support-widget-element.js';
14
+ /**
15
+ * Mints a guest session and mounts a configured widget element. Returns the element.
16
+ */
17
+ export async function mountWidget(options, deps = {}) {
18
+ defineSupportWidgetElement();
19
+ const client = (deps.sessionClientFactory ?? defaultSessionClient)(options.apiUrl, options.widgetKey);
20
+ // Returning-visitor anchor (RV1): present the durable cookie (if any) so the server can chain this
21
+ // visit to the visitor's prior conversation. Gated server-side on the widget's RememberReturningVisitors
22
+ // toggle — when off, the server returns no visitorKey and we set no cookie below.
23
+ const presentedVisitorKey = readVisitorKey(options.widgetKey);
24
+ const session = await client.Mint(presentedVisitorKey);
25
+ if (session.rememberReturningVisitors && session.visitorKey) {
26
+ writeVisitorKey(options.widgetKey, session.visitorKey);
27
+ }
28
+ const transport = await (deps.transportFactory ?? defaultTransport)(options.apiUrl);
29
+ await transport.Initialize(session);
30
+ const element = document.createElement(WIDGET_TAG_NAME);
31
+ element.Configure({ title: options.title, greeting: options.greeting });
32
+ element.SetSession(session);
33
+ element.SetTransport(transport);
34
+ if (session.modality === 'Voice' || session.modality === 'Both') {
35
+ element.SetVoiceController(await (deps.voiceControllerFactory ?? defaultVoiceController)(session));
36
+ }
37
+ // RV5 "forget me": only wired when the widget remembers returning visitors and a durable key exists.
38
+ // Archives the visitor's server-side memory, then clears the first-party cookie so no linkage remains.
39
+ if (session.rememberReturningVisitors && session.visitorKey) {
40
+ const visitorKey = session.visitorKey;
41
+ element.SetForgetHandler(async () => {
42
+ const result = await client.Forget(visitorKey);
43
+ if (!result.success) {
44
+ throw new Error(result.error ?? 'forget failed');
45
+ }
46
+ clearVisitorKey(options.widgetKey);
47
+ });
48
+ }
49
+ resolveMountTarget(options.mountTarget).appendChild(element);
50
+ registerNotificationAdapter(element);
51
+ scheduleRefresh(client, transport, element, session, deps.scheduler ?? defaultScheduler);
52
+ return element;
53
+ }
54
+ /** Reads data-attributes from the host and mounts. Safe to call once on script load. */
55
+ export async function bootstrapFromDocument(doc = document) {
56
+ const mountEl = doc.querySelector('[data-widget-key]');
57
+ const widgetKey = mountEl?.dataset.widgetKey;
58
+ const apiUrl = mountEl?.dataset.apiUrl;
59
+ if (!mountEl || !widgetKey || !apiUrl) {
60
+ // Nothing to mount — the host hasn't placed a configured element. Not an error.
61
+ return null;
62
+ }
63
+ return mountWidget({
64
+ widgetKey,
65
+ apiUrl,
66
+ mountTarget: mountEl,
67
+ title: mountEl.dataset.title,
68
+ greeting: mountEl.dataset.greeting,
69
+ });
70
+ }
71
+ function defaultSessionClient(apiUrl, widgetKey) {
72
+ return new WidgetSessionClient(apiUrl, widgetKey);
73
+ }
74
+ /**
75
+ * Default transport. DYNAMIC import (CLAUDE rule 8, category 3 — bundle-size deferral): the runtime
76
+ * transport pulls in `@memberjunction/graphql-dataprovider` + `@memberjunction/conversations-runtime`
77
+ * (the heaviest dependency in the bundle). Loading it on demand keeps it OUT of the embed entry chunk
78
+ * so the launcher button paints before the runtime is fetched. Declared in `dependencies`.
79
+ */
80
+ async function defaultTransport(apiUrl) {
81
+ const { RuntimeWidgetTransport } = await import('./transport/runtime-widget-transport.js');
82
+ return new RuntimeWidgetTransport(apiUrl);
83
+ }
84
+ /**
85
+ * Default voice controller. DYNAMIC import (CLAUDE rule 8, category 3): the realtime controller pulls
86
+ * in `@memberjunction/ai-realtime-client` + provider drivers, needed ONLY for Voice/Both widgets — so
87
+ * a text-only widget never pays for the voice chunk. Declared in `dependencies`.
88
+ */
89
+ async function defaultVoiceController(session) {
90
+ const [{ RealtimeVoiceController }, { createGuestVoiceMint }, { createGuestToolRelay }] = await Promise.all([
91
+ import('./voice/realtime-voice-controller.js'),
92
+ import('./voice/guest-voice-mint.js'),
93
+ import('./voice/guest-tool-relay.js'),
94
+ ]);
95
+ return new RealtimeVoiceController(createGuestVoiceMint(session), voiceLimitsForSession(session),
96
+ // Phase 2: the interactive channels this widget may attach (Whiteboard, …) + the live relay for
97
+ // non-channel tool calls. Empty enabledChannels (the default) leaves voice behavior unchanged.
98
+ session.enabledChannels, createGuestToolRelay());
99
+ }
100
+ /**
101
+ * Derives the client-side voice ceilings from the widget instance's configured
102
+ * VoiceMaxSessionMinutes (surfaced on the minted session). When the deployment sets no
103
+ * per-widget cap, the guard's built-in defaults apply. This is defense-in-depth — the
104
+ * authoritative cap is enforced server-side at the realtime mint (the ephemeral session's
105
+ * bounded TTL), so a tampered client cannot exceed the deployment's limit.
106
+ */
107
+ function voiceLimitsForSession(session) {
108
+ if (session.voiceMaxSessionMinutes && session.voiceMaxSessionMinutes > 0) {
109
+ return { ...DEFAULT_VOICE_LIMITS, maxSessionMinutes: session.voiceMaxSessionMinutes };
110
+ }
111
+ return undefined;
112
+ }
113
+ function defaultScheduler(cb, delayMs) {
114
+ setTimeout(cb, delayMs);
115
+ }
116
+ /** Resolves a selector/element/undefined to the mount parent (falls back to <body>). */
117
+ function resolveMountTarget(target) {
118
+ if (target instanceof HTMLElement)
119
+ return target;
120
+ if (typeof target === 'string') {
121
+ const found = document.querySelector(target);
122
+ if (found)
123
+ return found;
124
+ }
125
+ return document.body;
126
+ }
127
+ /**
128
+ * Routes runtime warnings/errors into the widget transcript as system lines. DYNAMIC import
129
+ * (CLAUDE rule 8, category 3): `ConversationsRuntime` is part of the heavy runtime chunk; importing
130
+ * it here (rather than statically at module top) keeps it out of the embed entry chunk. Fire-and-forget
131
+ * — adapter registration is best-effort and must not block the mount. Declared in `dependencies`.
132
+ */
133
+ function registerNotificationAdapter(element) {
134
+ void import('@memberjunction/conversations-runtime').then(({ ConversationsRuntime }) => {
135
+ ConversationsRuntime.Instance.UseNotificationAdapter({
136
+ Notify: (level, message) => {
137
+ if (level === 'error' || level === 'warning') {
138
+ element.ShowSystemMessage(message);
139
+ }
140
+ },
141
+ });
142
+ });
143
+ }
144
+ /** Refreshes the guest token shortly before expiry and re-arms the timer. */
145
+ function scheduleRefresh(client, transport, element, session, scheduler) {
146
+ const delay = WidgetSessionClient.MsUntilRefresh(session, Date.now());
147
+ scheduler(() => {
148
+ void (async () => {
149
+ try {
150
+ const refreshed = await client.Refresh(session.visitorKey);
151
+ transport.UpdateToken(refreshed.token);
152
+ scheduleRefresh(client, transport, element, refreshed, scheduler);
153
+ }
154
+ catch {
155
+ // A failed refresh leaves the current (soon-to-expire) token. Surface the
156
+ // connection-lost banner so the visitor can retry the refresh; on success
157
+ // the banner clears and the refresh timer re-arms (graceful degradation, W6).
158
+ element.ShowConnectionError('Connection to support was lost.', () => scheduleRefresh(client, transport, element, session, scheduler));
159
+ }
160
+ })();
161
+ }, delay);
162
+ }
163
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @fileoverview The durable returning-visitor anchor (RV3): a long-lived, opaque,
3
+ * first-party cookie holding the server-minted `VisitorKey`. This is identity/auth
4
+ * state (a visitor anchor), NOT a user preference — so a cookie is the right mechanism
5
+ * (analogous to the auth-token exception, CLAUDE rule 9), and it must survive page
6
+ * close (unlike the in-memory guest token). It is ONLY ever set when the widget's
7
+ * `RememberReturningVisitors` toggle is on; the "forget me" control clears it (RV5).
8
+ *
9
+ * The cookie is scoped per widget key so two widgets on the same origin don't share a
10
+ * visitor identity. The value is the opaque base64url key the server returns.
11
+ *
12
+ * @module @memberjunction/realtime-widget
13
+ */
14
+ /** Reads the persisted VisitorKey for this widget, or undefined when absent / no document. */
15
+ export declare function readVisitorKey(widgetKey: string): string | undefined;
16
+ /** Persists the VisitorKey as a long-lived first-party cookie (Secure on https, SameSite=Lax). */
17
+ export declare function writeVisitorKey(widgetKey: string, visitorKey: string): void;
18
+ /** Clears the persisted VisitorKey (RV5 "forget me"). Expires the cookie immediately. */
19
+ export declare function clearVisitorKey(widgetKey: string): void;
20
+ //# sourceMappingURL=visitor-key-cookie.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visitor-key-cookie.d.ts","sourceRoot":"","sources":["../../src/session/visitor-key-cookie.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAcH,8FAA8F;AAC9F,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAiBpE;AAED,kGAAkG;AAClG,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAQ3E;AAED,yFAAyF;AACzF,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAKvD"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * @fileoverview The durable returning-visitor anchor (RV3): a long-lived, opaque,
3
+ * first-party cookie holding the server-minted `VisitorKey`. This is identity/auth
4
+ * state (a visitor anchor), NOT a user preference — so a cookie is the right mechanism
5
+ * (analogous to the auth-token exception, CLAUDE rule 9), and it must survive page
6
+ * close (unlike the in-memory guest token). It is ONLY ever set when the widget's
7
+ * `RememberReturningVisitors` toggle is on; the "forget me" control clears it (RV5).
8
+ *
9
+ * The cookie is scoped per widget key so two widgets on the same origin don't share a
10
+ * visitor identity. The value is the opaque base64url key the server returns.
11
+ *
12
+ * @module @memberjunction/realtime-widget
13
+ */
14
+ /** Cookie name prefix; the (sanitized) widget key is appended to scope it per deployment. */
15
+ const COOKIE_PREFIX = 'mjwv_';
16
+ /** Default cookie lifetime. The authoritative retention lives server-side (VisitorMemoryRetentionDays);
17
+ * this is just how long the browser keeps presenting the anchor. One year, refreshed on each visit. */
18
+ const COOKIE_MAX_AGE_SECONDS = 365 * 24 * 60 * 60;
19
+ /** Per-deployment cookie name; widget keys are restricted to a cookie-name-safe charset. */
20
+ function cookieName(widgetKey) {
21
+ return COOKIE_PREFIX + widgetKey.replace(/[^A-Za-z0-9_-]/g, '');
22
+ }
23
+ /** Reads the persisted VisitorKey for this widget, or undefined when absent / no document. */
24
+ export function readVisitorKey(widgetKey) {
25
+ if (typeof document === 'undefined' || !document.cookie) {
26
+ return undefined;
27
+ }
28
+ const name = cookieName(widgetKey) + '=';
29
+ for (const part of document.cookie.split(';')) {
30
+ const trimmed = part.trim();
31
+ if (trimmed.startsWith(name)) {
32
+ const raw = trimmed.slice(name.length);
33
+ try {
34
+ return decodeURIComponent(raw) || undefined;
35
+ }
36
+ catch {
37
+ return raw || undefined;
38
+ }
39
+ }
40
+ }
41
+ return undefined;
42
+ }
43
+ /** Persists the VisitorKey as a long-lived first-party cookie (Secure on https, SameSite=Lax). */
44
+ export function writeVisitorKey(widgetKey, visitorKey) {
45
+ if (typeof document === 'undefined' || !visitorKey) {
46
+ return;
47
+ }
48
+ const secure = typeof location !== 'undefined' && location.protocol === 'https:' ? '; Secure' : '';
49
+ document.cookie =
50
+ `${cookieName(widgetKey)}=${encodeURIComponent(visitorKey)}` +
51
+ `; Max-Age=${COOKIE_MAX_AGE_SECONDS}; Path=/; SameSite=Lax${secure}`;
52
+ }
53
+ /** Clears the persisted VisitorKey (RV5 "forget me"). Expires the cookie immediately. */
54
+ export function clearVisitorKey(widgetKey) {
55
+ if (typeof document === 'undefined') {
56
+ return;
57
+ }
58
+ document.cookie = `${cookieName(widgetKey)}=; Max-Age=0; Path=/; SameSite=Lax`;
59
+ }
60
+ //# sourceMappingURL=visitor-key-cookie.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visitor-key-cookie.js","sourceRoot":"","sources":["../../src/session/visitor-key-cookie.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,6FAA6F;AAC7F,MAAM,aAAa,GAAG,OAAO,CAAC;AAE9B;wGACwG;AACxG,MAAM,sBAAsB,GAAG,GAAG,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;AAElD,4FAA4F;AAC5F,SAAS,UAAU,CAAC,SAAiB;IACjC,OAAO,aAAa,GAAG,SAAS,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAC;AACpE,CAAC;AAED,8FAA8F;AAC9F,MAAM,UAAU,cAAc,CAAC,SAAiB;IAC5C,IAAI,OAAO,QAAQ,KAAK,WAAW,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;QACtD,OAAO,SAAS,CAAC;IACrB,CAAC;IACD,MAAM,IAAI,GAAG,UAAU,CAAC,SAAS,CAAC,GAAG,GAAG,CAAC;IACzC,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3B,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACvC,IAAI,CAAC;gBACD,OAAO,kBAAkB,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC;YAChD,CAAC;YAAC,MAAM,CAAC;gBACL,OAAO,GAAG,IAAI,SAAS,CAAC;YAC5B,CAAC;QACL,CAAC;IACL,CAAC;IACD,OAAO,SAAS,CAAC;AACrB,CAAC;AAED,kGAAkG;AAClG,MAAM,UAAU,eAAe,CAAC,SAAiB,EAAE,UAAkB;IACjE,IAAI,OAAO,QAAQ,KAAK,WAAW,IAAI,CAAC,UAAU,EAAE,CAAC;QACjD,OAAO;IACX,CAAC;IACD,MAAM,MAAM,GAAG,OAAO,QAAQ,KAAK,WAAW,IAAI,QAAQ,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;IACnG,QAAQ,CAAC,MAAM;QACX,GAAG,UAAU,CAAC,SAAS,CAAC,IAAI,kBAAkB,CAAC,UAAU,CAAC,EAAE;YAC5D,aAAa,sBAAsB,yBAAyB,MAAM,EAAE,CAAC;AAC7E,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,eAAe,CAAC,SAAiB;IAC7C,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;QAClC,OAAO;IACX,CAAC;IACD,QAAQ,CAAC,MAAM,GAAG,GAAG,UAAU,CAAC,SAAS,CAAC,oCAAoC,CAAC;AACnF,CAAC"}