@botfabrik/engine-webclient 4.96.9 → 4.96.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,13 +1,28 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
2
16
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
17
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
18
  };
5
19
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.CLIENT_TYPE = exports.Devices = void 0;
7
20
  const engine_domain_1 = require("@botfabrik/engine-domain");
8
21
  const engine_transcript_export_1 = require("@botfabrik/engine-transcript-export");
9
22
  const express_1 = require("express");
10
23
  const package_json_1 = require("../package.json");
24
+ const auth_1 = require("./auth");
25
+ const constants_1 = require("./constants");
11
26
  const createSessionInfo_1 = __importDefault(require("./createSessionInfo"));
12
27
  const extractLocale_1 = __importDefault(require("./extractLocale"));
13
28
  const getSupportedClientLocale_1 = __importDefault(require("./getSupportedClientLocale"));
@@ -16,16 +31,11 @@ const middleware_1 = __importDefault(require("./middleware"));
16
31
  const requestSessionData_1 = __importDefault(require("./requestSessionData"));
17
32
  const setTranslations_1 = __importDefault(require("./setTranslations"));
18
33
  const speechToText_1 = __importDefault(require("./speechToText"));
34
+ const types_1 = require("./types");
19
35
  const views_1 = __importDefault(require("./views"));
20
- var Devices;
21
- (function (Devices) {
22
- Devices["All"] = "all";
23
- Devices["Mobile"] = "mobile";
24
- Devices["Desktop"] = "desktop";
25
- Devices["None"] = "none";
26
- })(Devices || (exports.Devices = Devices = {}));
36
+ __exportStar(require("./types"), exports);
27
37
  exports.default = (clientName, environment, props) => async (bot) => {
28
- const logger = bot.logger.child({ clientType: exports.CLIENT_TYPE, clientName });
38
+ const logger = bot.logger.child({ clientType: constants_1.CLIENT_TYPE, clientName });
29
39
  // serve transcript pdf
30
40
  bot.webserver.express.use('/transcript-pdf/:sessionId', async (req, res) => {
31
41
  const sessionId = req.params['sessionId'];
@@ -79,6 +89,11 @@ exports.default = (clientName, environment, props) => async (bot) => {
79
89
  sendConfigurationToClient(socket, props, bot, clientName);
80
90
  socket.on('start-chat', onStartChat(socket, props, bot, clientName, environment, logger));
81
91
  socket.on('terminate-session', onTerminateSession(socket, bot));
92
+ if (props.auth) {
93
+ socket.on('login-requested', (data) => {
94
+ (0, auth_1.storeLoginRequestToken)(data.loginRequestToken, socket.id);
95
+ });
96
+ }
82
97
  }
83
98
  catch (error) {
84
99
  logger.error('Error while connecting webclient with backend.');
@@ -87,6 +102,9 @@ exports.default = (clientName, environment, props) => async (bot) => {
87
102
  socket.emit('action', engine_domain_1.Actions.sendMessageToGuest(errorMessage));
88
103
  }
89
104
  });
105
+ if (props.auth) {
106
+ (0, auth_1.setUpSamlAuth)(bot, props.auth, clientName, nsp);
107
+ }
90
108
  const client = {
91
109
  name: `${clientName}Webclient`,
92
110
  middleware: (0, middleware_1.default)(clientName, nsp),
@@ -116,59 +134,69 @@ const onTerminateSession = (socket, bot) => async ({ sessionId, // passed if the
116
134
  socket.leave(sessionId);
117
135
  await session.dispatch(engine_domain_1.Actions.guestDisconnected(sessionId));
118
136
  };
119
- const onStartChat = (socket, props, bot, clientName, environment, logger) => async ({ sessionId: sessionIdFromClient, userId: defaultUserId, querystrings, }) => {
120
- const locale = (0, extractLocale_1.default)(querystrings, socket.request.headers['accept-language']);
121
- const sessionsCollection = bot.store.db.collection('sessions');
122
- const { sessionId, sessionInfo: defaultSessionInfo, isNew, } = await (0, requestSessionData_1.default)(sessionIdFromClient, querystrings, sessionsCollection, clientName, props);
123
- // create a channel for each session
124
- socket.join(sessionId);
125
- const sessionInfo = await (0, createSessionInfo_1.default)(socket, clientName, environment, defaultSessionInfo, defaultUserId, locale, querystrings, props)();
126
- const session = await bot.createSession(sessionId, sessionInfo);
127
- sendConfigurationToClient(socket, props, bot, clientName);
128
- // sending persisted state to client
129
- const previousConversations = await (0, loadPreviousConversation_1.default)(bot.store, sessionId);
130
- socket.emit('restore-client-state', {
131
- sessionId,
132
- messages: previousConversations,
133
- });
134
- // enable conversation rating
135
- if (props.enableConversationRating) {
136
- socket.emit('enable-conversation-rating', {
137
- rating: sessionInfo.client.payload.conversationRating,
137
+ const onStartChat = (socket, props, bot, clientName, environment, logger) => async ({ sessionId: sessionIdFromClient, userId: defaultUserId, querystrings, loginToken, }) => {
138
+ try {
139
+ const authenticatedUser = (0, auth_1.verifyLoginToken)(loginToken, props.auth, logger);
140
+ const locale = (0, extractLocale_1.default)(querystrings, socket.request.headers['accept-language']);
141
+ const sessionsCollection = bot.store.db.collection('sessions');
142
+ const { sessionId, sessionInfo: defaultSessionInfo, isNew, } = await (0, requestSessionData_1.default)(sessionIdFromClient, querystrings, sessionsCollection, clientName, props);
143
+ // create a channel for each session
144
+ socket.join(sessionId);
145
+ const sessionInfo = await (0, createSessionInfo_1.default)(socket, clientName, environment, defaultSessionInfo, defaultUserId, locale, querystrings, authenticatedUser, props)();
146
+ const session = await bot.createSession(sessionId, sessionInfo);
147
+ sendConfigurationToClient(socket, props, bot, clientName);
148
+ // sending persisted state to client
149
+ const previousConversations = await (0, loadPreviousConversation_1.default)(bot.store, sessionId);
150
+ socket.emit('restore-client-state', {
151
+ sessionId,
152
+ messages: previousConversations,
138
153
  });
139
- }
140
- // Notify middlewares about a connected or reconnected guest
141
- await session.dispatch(engine_domain_1.Actions.guestConnected(sessionId));
142
- if (isNew && props.getStartedAction) {
143
- await session.dispatch(props.getStartedAction);
144
- }
145
- registerListener(socket, 'action', async (action) => {
146
- await session.dispatch(JSON.parse(action));
147
- });
148
- registerListener(socket, 'disconnect', async () => {
149
- await session.dispatch(engine_domain_1.Actions.guestDisconnected(sessionId));
150
- });
151
- registerListener(socket, 'conversation-rating', async (rating) => {
152
- sessionsCollection.updateOne({ _id: sessionId }, { $set: { 'sessionInfo.client.payload.conversationRating': rating } });
153
- session.dispatch({
154
- type: engine_domain_1.ActionTypes.CONVERSATION_RATING_FROM_GUEST,
155
- payload: { rating },
154
+ // enable conversation rating
155
+ if (props.enableConversationRating) {
156
+ socket.emit('enable-conversation-rating', {
157
+ rating: sessionInfo.client.payload.conversationRating,
158
+ });
159
+ }
160
+ // Notify middlewares about a connected or reconnected guest
161
+ await session.dispatch(engine_domain_1.Actions.guestConnected(sessionId));
162
+ if (isNew && props.getStartedAction) {
163
+ await session.dispatch(props.getStartedAction);
164
+ }
165
+ registerListener(socket, 'action', async (action) => {
166
+ await session.dispatch(JSON.parse(action));
156
167
  });
157
- });
158
- registerListener(socket, 'audio-message', async (buffer) => {
159
- if (props.speech) {
160
- try {
161
- const text = await (0, speechToText_1.default)(props.speech, session.translator.locale, buffer);
162
- if (text && text.length) {
163
- socket.emit('speech-transcription', text);
164
- session.dispatch(engine_domain_1.Actions.receiveTextMessageFromGuest(text));
168
+ registerListener(socket, 'disconnect', async () => {
169
+ await session.dispatch(engine_domain_1.Actions.guestDisconnected(sessionId));
170
+ });
171
+ registerListener(socket, 'conversation-rating', async (rating) => {
172
+ sessionsCollection.updateOne({ _id: sessionId }, {
173
+ $set: { 'sessionInfo.client.payload.conversationRating': rating },
174
+ });
175
+ session.dispatch({
176
+ type: engine_domain_1.ActionTypes.CONVERSATION_RATING_FROM_GUEST,
177
+ payload: { rating },
178
+ });
179
+ });
180
+ registerListener(socket, 'audio-message', async (buffer) => {
181
+ if (props.speech) {
182
+ try {
183
+ const text = await (0, speechToText_1.default)(props.speech, session.translator.locale, buffer);
184
+ if (text && text.length) {
185
+ socket.emit('speech-transcription', text);
186
+ session.dispatch(engine_domain_1.Actions.receiveTextMessageFromGuest(text));
187
+ }
188
+ }
189
+ catch (error) {
190
+ logger.error(error);
165
191
  }
166
192
  }
167
- catch (error) {
168
- logger.error(error);
169
- }
170
- }
171
- });
193
+ });
194
+ }
195
+ catch (error) {
196
+ logger.error('Error while starting chat session:', error);
197
+ socket.disconnect(true);
198
+ return;
199
+ }
172
200
  };
173
201
  const registerListener = (socket, event, listener) => {
174
202
  // remove all listeners for the event to avoid multiple listeners
@@ -183,7 +211,8 @@ const sendConfigurationToClient = (socket, props, bot, clientName) => {
183
211
  (0, setTranslations_1.default)(socket, bot, clientLocale, clientName);
184
212
  const settings = {
185
213
  version: package_json_1.version,
186
- enableStartScreen: !!props.enableStartScreen,
214
+ requiresUserAuthentication: !!props.auth,
215
+ enableStartScreen: !!props.enableStartScreen || !!props.auth,
187
216
  enableStandaloneView: !!props.enableStandaloneView,
188
217
  enableTranscriptExportPdf: !!props.enableTranscriptExportPdf,
189
218
  enableTranscriptExportEmail: !!props.enableTranscriptExportEmail,
@@ -194,11 +223,10 @@ const sendConfigurationToClient = (socket, props, bot, clientName) => {
194
223
  };
195
224
  socket.emit('set-settings', settings);
196
225
  if (props.expandChatWindowAtStart &&
197
- props.expandChatWindowAtStart !== Devices.None) {
226
+ props.expandChatWindowAtStart !== types_1.Devices.None) {
198
227
  socket.emit('expand-window', {
199
228
  devices: props.expandChatWindowAtStart,
200
229
  initial: true,
201
230
  });
202
231
  }
203
232
  };
204
- exports.CLIENT_TYPE = 'webclient';
@@ -1,5 +1,5 @@
1
1
  import { type Middleware } from '@botfabrik/engine-domain';
2
2
  import type { Namespace } from 'socket.io';
3
- import type { WebclientMiddlewareState } from '../types';
3
+ import { type WebclientMiddlewareState } from '../types';
4
4
  declare const _default: (clientName: string, nsp: Namespace) => Middleware<WebclientMiddlewareState>;
5
5
  export default _default;
@@ -5,8 +5,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const engine_domain_1 = require("@botfabrik/engine-domain");
7
7
  const engine_utils_1 = require("@botfabrik/engine-utils");
8
- const __1 = require("..");
9
8
  const setTranslations_1 = __importDefault(require("../setTranslations"));
9
+ const types_1 = require("../types");
10
10
  exports.default = (clientName, nsp) => async (bot) => {
11
11
  return async (next, _state, action, session) => {
12
12
  const sessionInfo = session.getSessionInfo();
@@ -57,7 +57,7 @@ const sendToChatClient = async (nsp, session, action) => {
57
57
  };
58
58
  const expandChatWindow = (nsp, session, action) => {
59
59
  const devices = action.payload?.devices;
60
- if (!!devices && devices !== __1.Devices.None) {
60
+ if (!!devices && devices !== types_1.Devices.None) {
61
61
  nsp
62
62
  .to(session.id)
63
63
  .emit('expand-window', { devices: action.payload.devices });
@@ -1,6 +1,5 @@
1
1
  import type { SessionInfo } from '@botfabrik/engine-domain';
2
- import { type WebClientProps } from './index';
3
- import type { SessionInfoClientPayload, SessionInfoUserPayload } from './types';
2
+ import type { SessionInfoClientPayload, SessionInfoUserPayload, WebClientProps } from './types';
4
3
  interface SessionData {
5
4
  sessionId: string;
6
5
  sessionInfo: SessionInfo<SessionInfoClientPayload, SessionInfoUserPayload>;
@@ -1,10 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const crypto_1 = require("crypto");
4
- const index_1 = require("./index");
4
+ const constants_1 = require("./constants");
5
5
  const requestSessionData = async (sessionId, querystrings, sessionsCollection, clientName, props) => {
6
6
  const baseQuery = {
7
- 'sessionInfo.client.type': index_1.CLIENT_TYPE,
7
+ 'sessionInfo.client.type': constants_1.CLIENT_TYPE,
8
8
  'sessionInfo.client.name': clientName,
9
9
  };
10
10
  let findSessionRecordQuery;
@@ -1,7 +1,3 @@
1
- export interface SpeechToTextProps {
2
- clientEmail: string;
3
- privateKey: string;
4
- contextPhrases?: any;
5
- }
1
+ import type { SpeechToTextProps } from './types';
6
2
  declare const speechToText: (speechProps: SpeechToTextProps, locale: string, speechBytes: any) => Promise<string>;
7
3
  export default speechToText;
package/dist/types.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { Action, SessionInfoUser } from '@botfabrik/engine-domain';
1
2
  export type SessionInfoClientPayload = {
2
3
  querystrings: any;
3
4
  referrer: string;
@@ -9,3 +10,172 @@ export type ConversationRating = {
9
10
  };
10
11
  export type SessionInfoUserPayload = {};
11
12
  export type WebclientMiddlewareState = {};
13
+ export type RequestUserInfos = (querystrings: any) => Promise<Partial<SessionInfoUser<SessionInfoUserPayload>>>;
14
+ export type RequestSessionRecordQuery = (params: {
15
+ querystrings: any;
16
+ sessionId: string;
17
+ }) => Promise<Record<string, unknown>>;
18
+ export type MenuItemType = 'link' | 'action';
19
+ export type MenuItemIcon = 'mail' | 'phone' | 'scroll' | 'link';
20
+ export type MenuItemVisibility = 'always' | 'chat-only' | 'start-screen-only';
21
+ type BaseMenuItem = {
22
+ /**
23
+ * Unique key for this menu item.
24
+ * Used for translation as `header.menu.<id>`.
25
+ */
26
+ id: string;
27
+ /**
28
+ * Icon displayed for this menu item.
29
+ */
30
+ icon: MenuItemIcon;
31
+ /**
32
+ * If true, a divider (line) will be displayed after this item in the menu.
33
+ */
34
+ withDivider?: boolean;
35
+ /**
36
+ * Visibility of the menu item.
37
+ * - `always`: visible in all contexts (start screen, chat)
38
+ * - `chat-only`: visible only in the chat context
39
+ * - `start-screen-only`: visible only in the start screen context
40
+ */
41
+ visibility: MenuItemVisibility;
42
+ };
43
+ /**
44
+ * Represents a menu item that points to a web link.
45
+ */
46
+ type LinkMenuItem = BaseMenuItem & {
47
+ type: 'link';
48
+ /**
49
+ * The URL that the menu item points to.
50
+ */
51
+ url: string;
52
+ };
53
+ /**
54
+ * Represents a menu item that triggers an action.
55
+ */
56
+ type ActionMenuItem = BaseMenuItem & {
57
+ type: 'action';
58
+ /**
59
+ * The action to be executed when the menu item is clicked.
60
+ */
61
+ action: Action;
62
+ };
63
+ export type MenuItem = LinkMenuItem | ActionMenuItem;
64
+ export interface SpeechToTextProps {
65
+ clientEmail: string;
66
+ privateKey: string;
67
+ contextPhrases?: any;
68
+ }
69
+ export type SamlAuthConfig = {
70
+ entryPoint: string;
71
+ issuer: string;
72
+ idpCert: string | string[];
73
+ /**
74
+ * Options are passed to the passport-saml strategy.
75
+ */
76
+ options?: Record<string, unknown>;
77
+ };
78
+ export type Auth = {
79
+ /**
80
+ * JWT secret used to sign the login token.
81
+ */
82
+ jwtSecret: string;
83
+ /**
84
+ * SAML authentication configuration.
85
+ */
86
+ saml: SamlAuthConfig;
87
+ };
88
+ export interface WebClientProps {
89
+ getStartedAction?: Action;
90
+ requestUserInfos?: RequestUserInfos;
91
+ requestSessionRecordQuery?: RequestSessionRecordQuery;
92
+ speech?: SpeechToTextProps | undefined;
93
+ expandChatWindowAtStart?: Devices;
94
+ fabVisible?: boolean;
95
+ /**
96
+ * SAML authentication configuration for the web client.
97
+ * If set, users must authenticate via SAML before accessing the chat.
98
+ * If omitted, the chat is accessible without authentication.
99
+ *
100
+ * @default undefined
101
+ */
102
+ auth?: Auth;
103
+ /**
104
+ * Displays a start screen before a new chat begins, useful for user opt-in (e.g., terms acceptance).
105
+ * Contains a text (`start-screen.intro` in Markdown), form fields, and a start button (`start-screen.start-chat`).
106
+ * No user data is collected until the user clicks the start button.
107
+ *
108
+ * @default false (unless `auth` is set)
109
+ */
110
+ enableStartScreen?: boolean;
111
+ /**
112
+ * Enables the conversation rating feature.
113
+ * When set to true, users will be prompted to rate the conversation
114
+ * on a scale from 1 to 5 stars and optionally provide written feedback.
115
+ *
116
+ * Useful for collecting quality insights, improving support, or tracking user satisfaction.
117
+ *
118
+ * @default false
119
+ */
120
+ enableConversationRating?: boolean;
121
+ /**
122
+ * Enables the option to export the chat transcript as a PDF file.
123
+ *
124
+ * When set to `true`, users can download the current chat conversation
125
+ * as a PDF document.
126
+ *
127
+ * @default false
128
+ */
129
+ enableTranscriptExportPdf?: boolean;
130
+ /**
131
+ * Enables the option to send the chat transcript to the user via email.
132
+ *
133
+ * When set to `true`, users can request the current chat conversation
134
+ * to be sent to their email address.
135
+ *
136
+ * @default false
137
+ */
138
+ enableTranscriptExportEmail?: boolean;
139
+ /**
140
+ * Enables the option to open the chat client in a standalone tab or window.
141
+ * When set to true, a button will be shown that allows users to open the chat
142
+ * outside of an embedded iframe, in a new browser tab.
143
+ *
144
+ * @default false
145
+ */
146
+ enableStandaloneView?: boolean;
147
+ /**
148
+ * Enables the display of general terms and conditions above the chat window.
149
+ * When set to true, a section containing general conditions (e.g. legal notice,
150
+ * consent information, disclaimers) will be shown above the chat interface.
151
+ *
152
+ * Useful for compliance, legal requirements, or informing users before starting a conversation.
153
+ *
154
+ * @default false
155
+ * */
156
+ enableGeneralConditions?: boolean;
157
+ /**
158
+ * List of available chat commands that can be used in the input field.
159
+ *
160
+ * Each command may trigger a predefined action, insert a message template,
161
+ * or activate a special feature.
162
+ *
163
+ * Useful for advanced chat interactions, power-user features, or slash commands (e.g. /help, /end).
164
+ *
165
+ * @default []
166
+ * */
167
+ commands?: string[];
168
+ /**
169
+ * List of custom menu items displayed in the chat header.
170
+ *
171
+ * @default []
172
+ */
173
+ menuItems?: MenuItem[];
174
+ }
175
+ export declare enum Devices {
176
+ All = "all",
177
+ Mobile = "mobile",
178
+ Desktop = "desktop",
179
+ None = "none"
180
+ }
181
+ export {};
package/dist/types.js CHANGED
@@ -1,2 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Devices = void 0;
4
+ var Devices;
5
+ (function (Devices) {
6
+ Devices["All"] = "all";
7
+ Devices["Mobile"] = "mobile";
8
+ Devices["Desktop"] = "desktop";
9
+ Devices["None"] = "none";
10
+ })(Devices || (exports.Devices = Devices = {}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botfabrik/engine-webclient",
3
- "version": "4.96.9",
3
+ "version": "4.96.11",
4
4
  "description": "Webclient for Botfabriks Bot Engine",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -21,23 +21,27 @@
21
21
  },
22
22
  "dependencies": {
23
23
  "@botfabrik/engine-domain": "^4.96.6",
24
- "@botfabrik/engine-transcript-export": "^4.96.6",
25
- "@botfabrik/engine-utils": "^4.96.6",
24
+ "@botfabrik/engine-transcript-export": "^4.96.10",
25
+ "@botfabrik/engine-utils": "^4.96.10",
26
26
  "@google-cloud/speech": "^7.2.0",
27
+ "@node-saml/passport-saml": "^5.1.0",
27
28
  "accept-language-parser": "^1.5.0",
28
29
  "express": "^5.1.0",
30
+ "jsonwebtoken": "^9.0.2",
31
+ "passport": "^0.7.0",
29
32
  "uuid": "^11.1.0"
30
33
  },
31
34
  "devDependencies": {
32
35
  "@types/accept-language-parser": "^1.5.8",
33
36
  "@types/express": "^5.0.3",
37
+ "@types/jsonwebtoken": "^9.0.10",
34
38
  "@types/serve-static": "^1.15.8",
35
39
  "@types/ua-parser-js": "^0.7.39",
36
40
  "copyfiles": "^2.4.1",
37
41
  "rimraf": "^6.0.1",
38
42
  "socket.io": "^4.8.1",
39
43
  "tsx": "^4.20.3",
40
- "typescript": "5.8.3"
44
+ "typescript": "5.9.2"
41
45
  },
42
- "gitHead": "4e1e8a3d76989d4ba9000ca580c46236c04dc428"
46
+ "gitHead": "2d7dbdeb71d0a9e5567494da7927960b3309b5ac"
43
47
  }