@chanl/widget-sdk 0.2.0-canary.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 (128) hide show
  1. package/README.md +257 -0
  2. package/dist/auth.d.ts +26 -0
  3. package/dist/auth.d.ts.map +1 -0
  4. package/dist/auth.js +36 -0
  5. package/dist/auth.js.map +1 -0
  6. package/dist/chat/chat-client.d.ts +81 -0
  7. package/dist/chat/chat-client.d.ts.map +1 -0
  8. package/dist/chat/chat-client.js +192 -0
  9. package/dist/chat/chat-client.js.map +1 -0
  10. package/dist/chat/stream-parser.d.ts +20 -0
  11. package/dist/chat/stream-parser.d.ts.map +1 -0
  12. package/dist/chat/stream-parser.js +134 -0
  13. package/dist/chat/stream-parser.js.map +1 -0
  14. package/dist/chat/widget-config.d.ts +7 -0
  15. package/dist/chat/widget-config.d.ts.map +1 -0
  16. package/dist/chat/widget-config.js +26 -0
  17. package/dist/chat/widget-config.js.map +1 -0
  18. package/dist/client.d.ts +66 -0
  19. package/dist/client.d.ts.map +1 -0
  20. package/dist/client.js +49 -0
  21. package/dist/client.js.map +1 -0
  22. package/dist/defaults.d.ts +12 -0
  23. package/dist/defaults.d.ts.map +1 -0
  24. package/dist/defaults.js +27 -0
  25. package/dist/defaults.js.map +1 -0
  26. package/dist/embed/loader-types.d.ts +119 -0
  27. package/dist/embed/loader-types.d.ts.map +1 -0
  28. package/dist/embed/loader-types.js +20 -0
  29. package/dist/embed/loader-types.js.map +1 -0
  30. package/dist/embed/loader.d.ts +101 -0
  31. package/dist/embed/loader.d.ts.map +1 -0
  32. package/dist/embed/loader.js +439 -0
  33. package/dist/embed/loader.js.map +1 -0
  34. package/dist/embed/v1.global.js +5 -0
  35. package/dist/embed/v1.global.js.map +1 -0
  36. package/dist/events.d.ts +10 -0
  37. package/dist/events.d.ts.map +1 -0
  38. package/dist/events.js +25 -0
  39. package/dist/events.js.map +1 -0
  40. package/dist/index.d.ts +19 -0
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.js +29 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/logger.d.ts +14 -0
  45. package/dist/logger.d.ts.map +1 -0
  46. package/dist/logger.js +3 -0
  47. package/dist/logger.js.map +1 -0
  48. package/dist/next/index.d.ts +58 -0
  49. package/dist/next/index.d.ts.map +1 -0
  50. package/dist/next/index.js +83 -0
  51. package/dist/next/index.js.map +1 -0
  52. package/dist/react/index.d.ts +16 -0
  53. package/dist/react/index.d.ts.map +1 -0
  54. package/dist/react/index.js +20 -0
  55. package/dist/react/index.js.map +1 -0
  56. package/dist/react/types.d.ts +27 -0
  57. package/dist/react/types.d.ts.map +1 -0
  58. package/dist/react/types.js +8 -0
  59. package/dist/react/types.js.map +1 -0
  60. package/dist/react/use-chanl.d.ts +27 -0
  61. package/dist/react/use-chanl.d.ts.map +1 -0
  62. package/dist/react/use-chanl.js +57 -0
  63. package/dist/react/use-chanl.js.map +1 -0
  64. package/dist/react/use-chat.d.ts +32 -0
  65. package/dist/react/use-chat.d.ts.map +1 -0
  66. package/dist/react/use-chat.js +224 -0
  67. package/dist/react/use-chat.js.map +1 -0
  68. package/dist/react/use-voice.d.ts +37 -0
  69. package/dist/react/use-voice.d.ts.map +1 -0
  70. package/dist/react/use-voice.js +268 -0
  71. package/dist/react/use-voice.js.map +1 -0
  72. package/dist/react/widget.d.ts +43 -0
  73. package/dist/react/widget.d.ts.map +1 -0
  74. package/dist/react/widget.js +188 -0
  75. package/dist/react/widget.js.map +1 -0
  76. package/dist/storage/session-storage.d.ts +48 -0
  77. package/dist/storage/session-storage.d.ts.map +1 -0
  78. package/dist/storage/session-storage.js +84 -0
  79. package/dist/storage/session-storage.js.map +1 -0
  80. package/dist/types.d.ts +140 -0
  81. package/dist/types.d.ts.map +1 -0
  82. package/dist/types.js +7 -0
  83. package/dist/types.js.map +1 -0
  84. package/dist/voice/audio-recorder.d.ts +43 -0
  85. package/dist/voice/audio-recorder.d.ts.map +1 -0
  86. package/dist/voice/audio-recorder.js +127 -0
  87. package/dist/voice/audio-recorder.js.map +1 -0
  88. package/dist/voice/index.d.ts +13 -0
  89. package/dist/voice/index.d.ts.map +1 -0
  90. package/dist/voice/index.js +16 -0
  91. package/dist/voice/index.js.map +1 -0
  92. package/dist/voice/mock-mode.d.ts +93 -0
  93. package/dist/voice/mock-mode.d.ts.map +1 -0
  94. package/dist/voice/mock-mode.js +375 -0
  95. package/dist/voice/mock-mode.js.map +1 -0
  96. package/dist/voice/transports/index.d.ts +5 -0
  97. package/dist/voice/transports/index.d.ts.map +1 -0
  98. package/dist/voice/transports/index.js +10 -0
  99. package/dist/voice/transports/index.js.map +1 -0
  100. package/dist/voice/transports/transport.d.ts +70 -0
  101. package/dist/voice/transports/transport.d.ts.map +1 -0
  102. package/dist/voice/transports/transport.js +12 -0
  103. package/dist/voice/transports/transport.js.map +1 -0
  104. package/dist/voice/transports/vapi.d.ts +147 -0
  105. package/dist/voice/transports/vapi.d.ts.map +1 -0
  106. package/dist/voice/transports/vapi.js +337 -0
  107. package/dist/voice/transports/vapi.js.map +1 -0
  108. package/dist/voice/transports/webrtc.d.ts +58 -0
  109. package/dist/voice/transports/webrtc.d.ts.map +1 -0
  110. package/dist/voice/transports/webrtc.js +318 -0
  111. package/dist/voice/transports/webrtc.js.map +1 -0
  112. package/dist/voice/transports/websocket.d.ts +39 -0
  113. package/dist/voice/transports/websocket.d.ts.map +1 -0
  114. package/dist/voice/transports/websocket.js +280 -0
  115. package/dist/voice/transports/websocket.js.map +1 -0
  116. package/dist/voice/types.d.ts +323 -0
  117. package/dist/voice/types.d.ts.map +1 -0
  118. package/dist/voice/types.js +41 -0
  119. package/dist/voice/types.js.map +1 -0
  120. package/dist/voice/utils.d.ts +22 -0
  121. package/dist/voice/utils.d.ts.map +1 -0
  122. package/dist/voice/utils.js +44 -0
  123. package/dist/voice/utils.js.map +1 -0
  124. package/dist/voice/voice-client.d.ts +231 -0
  125. package/dist/voice/voice-client.d.ts.map +1 -0
  126. package/dist/voice/voice-client.js +1187 -0
  127. package/dist/voice/voice-client.js.map +1 -0
  128. package/package.json +91 -0
@@ -0,0 +1,188 @@
1
+ "use strict";
2
+ 'use client';
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.ChanlWidget = ChanlWidget;
38
+ /**
39
+ * <ChanlWidget> — v1 drop-in React component wrapping the embed loader.
40
+ *
41
+ * Minimal by design: takes props, calls `ChanlChat.init()` on mount,
42
+ * `ChanlChat.destroy()` on unmount (deferred 150ms to survive React
43
+ * StrictMode double-mount in dev). No provider, no event bus, no
44
+ * programmatic API surface on the component itself — those land in v2
45
+ * via `<ChanlProvider>` + `useChanlClient()`.
46
+ *
47
+ * Identity (`user` + `userHash`) is forwarded to the iframe as URL params.
48
+ * Compute `userHash` server-side using `computeUserHash` from
49
+ * `@chanl/widget-sdk/next`:
50
+ *
51
+ * ```tsx
52
+ * // app/dashboard/page.tsx (React Server Component)
53
+ * import { computeUserHash } from '@chanl/widget-sdk/next';
54
+ * import { ChanlWidget } from '@chanl/widget-sdk/react';
55
+ *
56
+ * export default async function Page() {
57
+ * const user = await getCurrentUser(); // Firebase/Clerk/NextAuth/Auth0/custom
58
+ * const userHash = computeUserHash(user.id, process.env.CHANL_IDENTITY_SECRET!);
59
+ * return (
60
+ * <ChanlWidget
61
+ * pubKey={process.env.NEXT_PUBLIC_CHANL_PUB_KEY!}
62
+ * agentId="agent_xxx"
63
+ * user={{ externalId: user.id, email: user.email, name: user.name }}
64
+ * userHash={userHash}
65
+ * />
66
+ * );
67
+ * }
68
+ * ```
69
+ *
70
+ * Performance note: per react-best-practices `rerender-dependencies`,
71
+ * the re-init effect depends on primitive fields only (`externalId`,
72
+ * `theme`, etc.), not on the `user` object identity. If you pass a fresh
73
+ * `user` object on every parent render but the underlying primitives are
74
+ * stable, the widget will NOT re-init. Other user fields (email, name,
75
+ * avatarUrl, attributes) are captured at init time from the latest render
76
+ * — they flow in but do not trigger re-init by themselves.
77
+ */
78
+ const react_1 = require("react");
79
+ function ChanlWidget(props) {
80
+ const { pubKey, agentId, user, userHash, baseUrl, chatHost, theme, position, color, branding, agentName, agentSubtitle, agentInitials, agentAvatarUrl, greeting, placeholder, darkBg, privacyUrl, locale, } = props;
81
+ // Hold the latest config in a ref so the effect body reads the freshest
82
+ // props even when the effect re-runs for primitive-only dependency changes.
83
+ const latestPropsRef = (0, react_1.useRef)(props);
84
+ latestPropsRef.current = props;
85
+ // Defer destroy by 150ms — StrictMode's intentional double-mount in dev
86
+ // runs mount → unmount → mount within that window, and the second mount
87
+ // cancels the pending destroy from the first unmount.
88
+ const destroyTimer = (0, react_1.useRef)(null);
89
+ (0, react_1.useEffect)(() => {
90
+ if (typeof window === 'undefined')
91
+ return;
92
+ // Cancel any pending destroy from a previous cleanup (StrictMode).
93
+ if (destroyTimer.current) {
94
+ clearTimeout(destroyTimer.current);
95
+ destroyTimer.current = null;
96
+ }
97
+ // `cancelled` guards the async loader — if the component unmounts
98
+ // before the dynamic import resolves (fast navigation), skip init.
99
+ let cancelled = false;
100
+ const build = () => {
101
+ const p = latestPropsRef.current;
102
+ return {
103
+ agentId: p.agentId,
104
+ apiKey: p.pubKey,
105
+ user: p.user,
106
+ userHash: p.userHash,
107
+ baseUrl: p.baseUrl,
108
+ chatHost: p.chatHost,
109
+ theme: p.theme,
110
+ position: p.position,
111
+ color: p.color,
112
+ branding: p.branding,
113
+ agentName: p.agentName,
114
+ agentSubtitle: p.agentSubtitle,
115
+ agentInitials: p.agentInitials,
116
+ agentAvatarUrl: p.agentAvatarUrl,
117
+ greeting: p.greeting,
118
+ placeholder: p.placeholder,
119
+ darkBg: p.darkBg,
120
+ privacyUrl: p.privacyUrl,
121
+ locale: p.locale,
122
+ };
123
+ };
124
+ // Lazily load the loader module. Side-effect import runs the IIFE
125
+ // which registers `window.ChanlChat`. Stays out of the SSR bundle
126
+ // and avoids leaking `declare global` into type consumers.
127
+ void Promise.resolve().then(() => __importStar(require('../embed/loader'))).then(() => {
128
+ if (cancelled)
129
+ return;
130
+ const ChanlChat = window.ChanlChat;
131
+ if (!ChanlChat)
132
+ return;
133
+ if (ChanlChat._initialized) {
134
+ try {
135
+ ChanlChat.destroy();
136
+ }
137
+ catch {
138
+ // ignore
139
+ }
140
+ }
141
+ ChanlChat.init(build());
142
+ });
143
+ return () => {
144
+ cancelled = true;
145
+ destroyTimer.current = setTimeout(() => {
146
+ if (typeof window !== 'undefined') {
147
+ const live = window.ChanlChat;
148
+ if (live && live._initialized) {
149
+ try {
150
+ live.destroy();
151
+ }
152
+ catch {
153
+ // ignore
154
+ }
155
+ }
156
+ }
157
+ destroyTimer.current = null;
158
+ }, 150);
159
+ };
160
+ },
161
+ // Primitive-only dependencies per react-best-practices `rerender-dependencies`.
162
+ // Re-init fires when any of these change. Non-primitive user fields
163
+ // (email, name, avatarUrl, attributes) flow via the ref on the next
164
+ // init — they don't trigger re-init on their own.
165
+ [
166
+ agentId,
167
+ pubKey,
168
+ user?.externalId,
169
+ userHash,
170
+ baseUrl,
171
+ chatHost,
172
+ theme,
173
+ position,
174
+ color,
175
+ branding,
176
+ agentName,
177
+ agentSubtitle,
178
+ agentInitials,
179
+ agentAvatarUrl,
180
+ greeting,
181
+ placeholder,
182
+ darkBg,
183
+ privacyUrl,
184
+ locale,
185
+ ]);
186
+ return null;
187
+ }
188
+ //# sourceMappingURL=widget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget.js","sourceRoot":"","sources":["../../src/react/widget.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Widget Session Storage — localStorage helpers for the persistent-session
3
+ * feature (int-050).
4
+ *
5
+ * Lifecycle:
6
+ * 1. Widget loader on host page reads `_chanl_session_{publicKey}_{agentId}`
7
+ * on init.
8
+ * 2. If present, loader passes the token into the iframe URL so the
9
+ * chat-client can call GET /interactions/:id/resume.
10
+ * 3. On createSession or successful resume, the iframe posts the new
11
+ * token back to the loader via postMessage; loader writes it.
12
+ * 4. On 4xx from resume (signature mismatch, expired, deleted), the
13
+ * iframe posts `session-token-cleared`; loader clears the key.
14
+ *
15
+ * Per-publicKey + per-agent scoping: a single host site can mount multiple
16
+ * agents (sales + support widgets side-by-side) — each gets its own
17
+ * resumable session. publicKey is already in loader config; agentId is
18
+ * already in the widget mount call. No extra wiring needed to namespace.
19
+ *
20
+ * Failure mode: localStorage can throw (Safari private mode, quota
21
+ * exceeded, sandboxed iframe with storage blocked). All three helpers
22
+ * catch and fail-silent so the widget keeps working — just without
23
+ * resumability (= fresh session every load, the pre-int-050 behavior).
24
+ *
25
+ * Note: these helpers run in the LOADER's host-origin context. The chat
26
+ * iframe is on a different origin and CANNOT touch this localStorage —
27
+ * all I/O bounces through postMessage. Anyone tempted to call these from
28
+ * inside the iframe will succeed locally but the loader won't see the
29
+ * value because they're separate origin localStorage stores.
30
+ */
31
+ /**
32
+ * Read a previously-stored session token. Returns the raw JWT string, or
33
+ * `null` when no token exists, the storage call throws, or we're in a
34
+ * non-browser context (SSR).
35
+ */
36
+ export declare function readSessionToken(publicKey: string, agentId: string): string | null;
37
+ /**
38
+ * Persist a freshly-issued session token. No-op on storage error so the
39
+ * widget continues to function (just without resumability on next load).
40
+ */
41
+ export declare function writeSessionToken(publicKey: string, agentId: string, token: string): void;
42
+ /**
43
+ * Clear the stored token. Called when the server tells us the token is no
44
+ * longer valid (403/410/404) or when the host explicitly resets the
45
+ * conversation via the "New conversation" button.
46
+ */
47
+ export declare function clearSessionToken(publicKey: string, agentId: string): void;
48
+ //# sourceMappingURL=session-storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-storage.d.ts","sourceRoot":"","sources":["../../src/storage/session-storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAQH;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOlF;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAOzF;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAO1E"}
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ /**
3
+ * Widget Session Storage — localStorage helpers for the persistent-session
4
+ * feature (int-050).
5
+ *
6
+ * Lifecycle:
7
+ * 1. Widget loader on host page reads `_chanl_session_{publicKey}_{agentId}`
8
+ * on init.
9
+ * 2. If present, loader passes the token into the iframe URL so the
10
+ * chat-client can call GET /interactions/:id/resume.
11
+ * 3. On createSession or successful resume, the iframe posts the new
12
+ * token back to the loader via postMessage; loader writes it.
13
+ * 4. On 4xx from resume (signature mismatch, expired, deleted), the
14
+ * iframe posts `session-token-cleared`; loader clears the key.
15
+ *
16
+ * Per-publicKey + per-agent scoping: a single host site can mount multiple
17
+ * agents (sales + support widgets side-by-side) — each gets its own
18
+ * resumable session. publicKey is already in loader config; agentId is
19
+ * already in the widget mount call. No extra wiring needed to namespace.
20
+ *
21
+ * Failure mode: localStorage can throw (Safari private mode, quota
22
+ * exceeded, sandboxed iframe with storage blocked). All three helpers
23
+ * catch and fail-silent so the widget keeps working — just without
24
+ * resumability (= fresh session every load, the pre-int-050 behavior).
25
+ *
26
+ * Note: these helpers run in the LOADER's host-origin context. The chat
27
+ * iframe is on a different origin and CANNOT touch this localStorage —
28
+ * all I/O bounces through postMessage. Anyone tempted to call these from
29
+ * inside the iframe will succeed locally but the loader won't see the
30
+ * value because they're separate origin localStorage stores.
31
+ */
32
+ Object.defineProperty(exports, "__esModule", { value: true });
33
+ exports.readSessionToken = readSessionToken;
34
+ exports.writeSessionToken = writeSessionToken;
35
+ exports.clearSessionToken = clearSessionToken;
36
+ const KEY_PREFIX = '_chanl_session_';
37
+ function makeKey(publicKey, agentId) {
38
+ return `${KEY_PREFIX}${publicKey}_${agentId}`;
39
+ }
40
+ /**
41
+ * Read a previously-stored session token. Returns the raw JWT string, or
42
+ * `null` when no token exists, the storage call throws, or we're in a
43
+ * non-browser context (SSR).
44
+ */
45
+ function readSessionToken(publicKey, agentId) {
46
+ if (typeof window === 'undefined' || !window.localStorage)
47
+ return null;
48
+ try {
49
+ return window.localStorage.getItem(makeKey(publicKey, agentId));
50
+ }
51
+ catch {
52
+ return null;
53
+ }
54
+ }
55
+ /**
56
+ * Persist a freshly-issued session token. No-op on storage error so the
57
+ * widget continues to function (just without resumability on next load).
58
+ */
59
+ function writeSessionToken(publicKey, agentId, token) {
60
+ if (typeof window === 'undefined' || !window.localStorage)
61
+ return;
62
+ try {
63
+ window.localStorage.setItem(makeKey(publicKey, agentId), token);
64
+ }
65
+ catch {
66
+ // localStorage quota, sandboxed iframe, etc. Fail silent.
67
+ }
68
+ }
69
+ /**
70
+ * Clear the stored token. Called when the server tells us the token is no
71
+ * longer valid (403/410/404) or when the host explicitly resets the
72
+ * conversation via the "New conversation" button.
73
+ */
74
+ function clearSessionToken(publicKey, agentId) {
75
+ if (typeof window === 'undefined' || !window.localStorage)
76
+ return;
77
+ try {
78
+ window.localStorage.removeItem(makeKey(publicKey, agentId));
79
+ }
80
+ catch {
81
+ // ignore
82
+ }
83
+ }
84
+ //# sourceMappingURL=session-storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-storage.js","sourceRoot":"","sources":["../../src/storage/session-storage.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;;AAaH,4CAOC;AAMD,8CAOC;AAOD,8CAOC;AA7CD,MAAM,UAAU,GAAG,iBAAiB,CAAC;AAErC,SAAS,OAAO,CAAC,SAAiB,EAAE,OAAe;IACjD,OAAO,GAAG,UAAU,GAAG,SAAS,IAAI,OAAO,EAAE,CAAC;AAChD,CAAC;AAED;;;;GAIG;AACH,SAAgB,gBAAgB,CAAC,SAAiB,EAAE,OAAe;IACjE,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,CAAC,MAAM,CAAC,YAAY;QAAE,OAAO,IAAI,CAAC;IACvE,IAAI,CAAC;QACH,OAAO,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;IAClE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAgB,iBAAiB,CAAC,SAAiB,EAAE,OAAe,EAAE,KAAa;IACjF,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,CAAC,MAAM,CAAC,YAAY;QAAE,OAAO;IAClE,IAAI,CAAC;QACH,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC;IAClE,CAAC;IAAC,MAAM,CAAC;QACP,0DAA0D;IAC5D,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAgB,iBAAiB,CAAC,SAAiB,EAAE,OAAe;IAClE,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,CAAC,MAAM,CAAC,YAAY;QAAE,OAAO;IAClE,IAAI,CAAC;QACH,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;IAC9D,CAAC;IAAC,MAAM,CAAC;QACP,SAAS;IACX,CAAC;AACH,CAAC"}
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Chanl Chat SDK — Standalone types
3
+ * No external dependencies.
4
+ */
5
+ export interface ChatClientConfig {
6
+ apiKey: string;
7
+ baseUrl: string;
8
+ /**
9
+ * Identified end-user. When present, activates the widget identity
10
+ * path — backend verifies `userHash` as HMAC-SHA256(externalId, secret),
11
+ * upserts a Customer by externalId, and attaches customer context to the
12
+ * chat session. Custom attributes flow into the agent prompt as
13
+ * `{{customer:<key>}}` variables.
14
+ */
15
+ user?: ChanlUser;
16
+ /** HMAC-SHA256 of `user.externalId` using workspace identity secret. */
17
+ userHash?: string;
18
+ /**
19
+ * Stable anon-Lead identifier read from `localStorage._chanl_anon` on the
20
+ * chat-host iframe origin. Used when `user` is absent to persist memory
21
+ * across sessions for cold visitors.
22
+ */
23
+ anonymousId?: string;
24
+ }
25
+ /**
26
+ * Identified end-user for the widget identity path (Intercom-style).
27
+ *
28
+ * The host app resolves its own user via whatever auth system it already
29
+ * has (Firebase, Clerk, NextAuth, Auth0, custom) and passes the stable
30
+ * `externalId`. The `userHash` is computed server-side via
31
+ * `computeUserHash(externalId, CHANL_IDENTITY_SECRET)` from the
32
+ * `@chanl/widget-sdk/next` subpath.
33
+ */
34
+ export interface ChanlUser {
35
+ /**
36
+ * Stable external user identifier — any string the host app uses for
37
+ * the user in its own system (Firebase uid, Auth0 sub, database id, ...).
38
+ */
39
+ externalId: string;
40
+ email?: string;
41
+ name?: string;
42
+ avatarUrl?: string;
43
+ /**
44
+ * Custom attributes (plan, role, workspace id, etc.). Flow into the
45
+ * agent prompt as `customer:<key>` variables automatically.
46
+ */
47
+ attributes?: Record<string, unknown>;
48
+ }
49
+ export interface CreateSessionInput {
50
+ systemPrompt?: string;
51
+ variables?: Record<string, string>;
52
+ /** @deprecated prefer `user.externalId` for client-side embeds. */
53
+ customerId?: string;
54
+ /** HMAC-SHA256 of the identifier (externalId when `user` is present, otherwise customerId). */
55
+ userHash?: string;
56
+ /** Identified end-user — activates the widget identity path. */
57
+ user?: ChanlUser;
58
+ /**
59
+ * Anonymous browser identifier (Intercom Lead path). Stable UUID minted in
60
+ * `localStorage._chanl_anon` on the chat-host iframe origin so cold visitors
61
+ * persist across sessions even without a verified `user`. Backend upserts a
62
+ * Customer with `externalId = "anon_<uuid>"`. Ignored when `user` or
63
+ * `customerId` is present.
64
+ */
65
+ anonymousId?: string;
66
+ metadata?: Record<string, unknown>;
67
+ toolsetId?: string;
68
+ }
69
+ export interface ChatSession {
70
+ interactionId: string;
71
+ sessionId: string;
72
+ agentId: string;
73
+ agentName?: string;
74
+ model?: string;
75
+ createdAt: string;
76
+ /**
77
+ * Signed JWT (HS256) issued by the server. Widget loader stores this in
78
+ * localStorage keyed by publicKey+agentId and presents it back on resume
79
+ * calls so the server can verify session ownership. Absent when the
80
+ * workspace has no identitySecret configured — session still works but
81
+ * cannot be resumed across host-page refreshes.
82
+ */
83
+ sessionToken?: string;
84
+ }
85
+ /**
86
+ * Response from GET /interactions/:id/resume. Rehydrates the conversation
87
+ * view + carries a refreshed token (rolling TTL — widget MUST replace its
88
+ * stored token with the new value).
89
+ */
90
+ export interface ResumedChatSession extends ChatSession {
91
+ status: string;
92
+ messages: Array<{
93
+ role: 'user' | 'assistant' | 'system';
94
+ content: string;
95
+ timestamp?: string;
96
+ }>;
97
+ /** Refreshed JWT — overwrite the stored one in localStorage. */
98
+ sessionToken: string;
99
+ }
100
+ export interface ToolInvocation {
101
+ toolCallId: string;
102
+ toolName: string;
103
+ state: 'call' | 'result';
104
+ args?: Record<string, unknown>;
105
+ result?: unknown;
106
+ }
107
+ export interface MessagePart {
108
+ type: 'text' | 'tool-invocation';
109
+ text?: string;
110
+ toolInvocation?: ToolInvocation;
111
+ }
112
+ export interface ChatMessageResponse {
113
+ sessionId: string;
114
+ message: string;
115
+ parts?: MessagePart[];
116
+ toolCalls?: unknown[];
117
+ latencyMs?: number;
118
+ }
119
+ export interface WidgetConfig {
120
+ id: string;
121
+ agent: {
122
+ id: string;
123
+ name: string;
124
+ };
125
+ launcher: {
126
+ position: string;
127
+ modality: 'voice' | 'chat' | 'both';
128
+ };
129
+ style: {
130
+ primaryColor?: string;
131
+ backgroundColor?: string;
132
+ logoUrl?: string;
133
+ agentAvatar?: string;
134
+ };
135
+ banner?: {
136
+ header?: string;
137
+ description?: string;
138
+ };
139
+ }
140
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ /**
3
+ * Chanl Chat SDK — Standalone types
4
+ * No external dependencies.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";AAAA;;;GAGG"}
@@ -0,0 +1,43 @@
1
+ export interface AudioRecorderConfig {
2
+ /** Audio track constraints for getUserMedia */
3
+ trackConstraints?: MediaTrackConstraints;
4
+ /** Time slice in milliseconds for data chunks */
5
+ timeSlice?: number;
6
+ /** Callback for each chunk of audio data */
7
+ onData: (data: string) => void;
8
+ /** Callback for recording state changes */
9
+ onStateChange?: (state: RecordingState) => void;
10
+ /** Callback for errors */
11
+ onError?: (error: Error) => void;
12
+ }
13
+ export type RecordingState = 'inactive' | 'recording' | 'paused';
14
+ /**
15
+ * High-quality audio recorder using extendable-media-recorder
16
+ * Handles WAV encoding and streaming of audio data
17
+ */
18
+ export declare class AudioRecorder {
19
+ private recorder;
20
+ private stream;
21
+ private readonly config;
22
+ constructor({ trackConstraints, timeSlice, onData, onStateChange, onError, }: AudioRecorderConfig);
23
+ /**
24
+ * Initialize the WAV encoder and start recording
25
+ */
26
+ start(): Promise<void>;
27
+ /**
28
+ * Stop recording and cleanup resources
29
+ */
30
+ stop(): void;
31
+ /**
32
+ * Get current recording state
33
+ */
34
+ getState(): RecordingState;
35
+ /**
36
+ * Check if currently recording
37
+ */
38
+ isRecording(): boolean;
39
+ private handleDataAvailable;
40
+ private handleStop;
41
+ private cleanup;
42
+ }
43
+ //# sourceMappingURL=audio-recorder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audio-recorder.d.ts","sourceRoot":"","sources":["../../src/voice/audio-recorder.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,mBAAmB;IAClC,+CAA+C;IAC/C,gBAAgB,CAAC,EAAE,qBAAqB,CAAC;IACzC,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4CAA4C;IAC5C,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,2CAA2C;IAC3C,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IAChD,0BAA0B;IAC1B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,WAAW,GAAG,QAAQ,CAAC;AAEjE;;;GAGG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAA+B;IAC/C,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgC;gBAE3C,EACV,gBAA6C,EAC7C,SAAc,EACd,MAAM,EACN,aAAyB,EACzB,OAAoD,GACrD,EAAE,mBAAmB;IAUtB;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAoC5B;;OAEG;IACH,IAAI,IAAI,IAAI;IAcZ;;OAEG;IACH,QAAQ,IAAI,cAAc;IAI1B;;OAEG;IACH,WAAW,IAAI,OAAO;YAIR,mBAAmB;IAajC,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,OAAO;CAiBhB"}
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ /*
3
+ * AudioRecorder captures microphone audio, down-samples to 16 kHz mono and encodes
4
+ * to base64-encoded linear16 PCM chunks suitable for the backend.
5
+ *
6
+ * Note: implementation keeps things simple; production apps may want to use a
7
+ * WebAudio AudioWorklet for lower latency.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.AudioRecorder = void 0;
11
+ const extendable_media_recorder_1 = require("extendable-media-recorder");
12
+ const extendable_media_recorder_wav_encoder_1 = require("extendable-media-recorder-wav-encoder");
13
+ const utils_1 = require("./utils");
14
+ /**
15
+ * High-quality audio recorder using extendable-media-recorder
16
+ * Handles WAV encoding and streaming of audio data
17
+ */
18
+ class AudioRecorder {
19
+ constructor({ trackConstraints = { echoCancellation: true }, timeSlice = 10, onData, onStateChange = () => { }, onError = (e) => console.error('[AudioRecorder]', e), }) {
20
+ this.recorder = null;
21
+ this.stream = null;
22
+ this.config = {
23
+ trackConstraints,
24
+ timeSlice,
25
+ onData,
26
+ onStateChange,
27
+ onError,
28
+ };
29
+ }
30
+ /**
31
+ * Initialize the WAV encoder and start recording
32
+ */
33
+ async start() {
34
+ try {
35
+ // Register WAV encoder if not already registered
36
+ if (!extendable_media_recorder_1.MediaRecorder.isTypeSupported('audio/wav')) {
37
+ console.debug("[AudioRecorder] Registering WAV encoder");
38
+ await (0, extendable_media_recorder_1.register)(await (0, extendable_media_recorder_wav_encoder_1.connect)());
39
+ }
40
+ // Get audio stream
41
+ this.stream = await navigator.mediaDevices.getUserMedia({
42
+ audio: this.config.trackConstraints,
43
+ });
44
+ const micSettings = this.stream?.getAudioTracks()[0].getSettings();
45
+ console.debug('[AudioRecorder] Mic settings:', micSettings);
46
+ // Create recorder with WAV encoding
47
+ this.recorder = new extendable_media_recorder_1.MediaRecorder(this.stream, {
48
+ mimeType: 'audio/wav',
49
+ });
50
+ // Set up event handlers
51
+ this.recorder.ondataavailable = this.handleDataAvailable.bind(this);
52
+ this.recorder.onstop = this.handleStop.bind(this);
53
+ this.recorder.onerror = (event) => this.config.onError(new Error(event.error.message));
54
+ this.recorder.addEventListener('statechange', () => this.config.onStateChange(this.recorder?.state ?? 'inactive'));
55
+ // Start recording
56
+ this.recorder.start(this.config.timeSlice);
57
+ console.debug('[AudioRecorder] Started recording');
58
+ }
59
+ catch (error) {
60
+ this.config.onError(error instanceof Error ? error : new Error('Failed to start recording'));
61
+ throw error;
62
+ }
63
+ }
64
+ /**
65
+ * Stop recording and cleanup resources
66
+ */
67
+ stop() {
68
+ if (!this.recorder)
69
+ return;
70
+ try {
71
+ if (this.recorder.state === 'recording') {
72
+ this.recorder.stop();
73
+ }
74
+ this.cleanup();
75
+ console.debug('[AudioRecorder] Stopped recording');
76
+ }
77
+ catch (error) {
78
+ this.config.onError(error instanceof Error ? error : new Error('Failed to stop recording'));
79
+ }
80
+ }
81
+ /**
82
+ * Get current recording state
83
+ */
84
+ getState() {
85
+ return this.recorder?.state ?? 'inactive';
86
+ }
87
+ /**
88
+ * Check if currently recording
89
+ */
90
+ isRecording() {
91
+ return this.recorder?.state === 'recording';
92
+ }
93
+ async handleDataAvailable(event) {
94
+ if (!event.data.size)
95
+ return;
96
+ try {
97
+ const base64 = await (0, utils_1.blobToBase64)(event.data);
98
+ if (base64) {
99
+ this.config.onData(base64);
100
+ }
101
+ }
102
+ catch (error) {
103
+ this.config.onError(error instanceof Error ? error : new Error('Failed to convert audio data'));
104
+ }
105
+ }
106
+ handleStop() {
107
+ this.cleanup();
108
+ }
109
+ cleanup() {
110
+ // Stop all tracks
111
+ this.stream?.getTracks().forEach(track => track.stop());
112
+ this.stream = null;
113
+ // Clear recorder
114
+ if (this.recorder) {
115
+ this.recorder.onstop = null;
116
+ this.recorder.ondataavailable = null;
117
+ this.recorder.onerror = null;
118
+ this.recorder.removeEventListener('dataavailable', this.handleDataAvailable.bind(this));
119
+ this.recorder.removeEventListener('stop', this.handleStop.bind(this));
120
+ this.recorder.removeEventListener('error', () => { });
121
+ this.recorder.removeEventListener('statechange', () => { });
122
+ this.recorder = null;
123
+ }
124
+ }
125
+ }
126
+ exports.AudioRecorder = AudioRecorder;
127
+ //# sourceMappingURL=audio-recorder.js.map