@weldsuite/helpdesk-widget-sdk 1.0.2
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/LICENSE +21 -0
- package/README.md +824 -0
- package/dist/angular.d.ts +764 -0
- package/dist/angular.esm.js +2768 -0
- package/dist/angular.esm.js.map +1 -0
- package/dist/angular.js +2775 -0
- package/dist/angular.js.map +1 -0
- package/dist/index.d.ts +1428 -0
- package/dist/index.esm.js +2577 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +2620 -0
- package/dist/index.js.map +1 -0
- package/dist/index.umd.js +2626 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/react.d.ts +666 -0
- package/dist/react.esm.js +2479 -0
- package/dist/react.esm.js.map +1 -0
- package/dist/react.js +2489 -0
- package/dist/react.js.map +1 -0
- package/dist/vue-composables.esm.js +2442 -0
- package/dist/vue-composables.esm.js.map +1 -0
- package/dist/vue-composables.js +2445 -0
- package/dist/vue-composables.js.map +1 -0
- package/frameworks/svelte/HelpdeskWidget.svelte +101 -0
- package/frameworks/svelte/index.ts +46 -0
- package/frameworks/svelte/shims-svelte.d.ts +4 -0
- package/frameworks/vue/HelpdeskWidget.vue +46 -0
- package/frameworks/vue/index.ts +47 -0
- package/frameworks/vue/shims-vue.d.ts +5 -0
- package/frameworks/vue/useHelpdeskWidget.ts +90 -0
- package/package.json +102 -0
package/dist/angular.js
ADDED
|
@@ -0,0 +1,2775 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var core = require('@angular/core');
|
|
4
|
+
|
|
5
|
+
/******************************************************************************
|
|
6
|
+
Copyright (c) Microsoft Corporation.
|
|
7
|
+
|
|
8
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
9
|
+
purpose with or without fee is hereby granted.
|
|
10
|
+
|
|
11
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
12
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
13
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
14
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
15
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
16
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
17
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
18
|
+
***************************************************************************** */
|
|
19
|
+
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
function __esDecorate(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
23
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
24
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
25
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
26
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
27
|
+
var _, done = false;
|
|
28
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
29
|
+
var context = {};
|
|
30
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
31
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
32
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
33
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
34
|
+
if (kind === "accessor") {
|
|
35
|
+
if (result === void 0) continue;
|
|
36
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
37
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
38
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
39
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
40
|
+
}
|
|
41
|
+
else if (_ = accept(result)) {
|
|
42
|
+
if (kind === "field") initializers.unshift(_);
|
|
43
|
+
else descriptor[key] = _;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
47
|
+
done = true;
|
|
48
|
+
}
|
|
49
|
+
function __runInitializers(thisArg, initializers, value) {
|
|
50
|
+
var useValue = arguments.length > 2;
|
|
51
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
52
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
53
|
+
}
|
|
54
|
+
return useValue ? value : void 0;
|
|
55
|
+
}
|
|
56
|
+
function __setFunctionName(f, name, prefix) {
|
|
57
|
+
if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
|
|
58
|
+
return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
|
|
59
|
+
}
|
|
60
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
61
|
+
var e = new Error(message);
|
|
62
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Weld SDK - Configuration Types
|
|
67
|
+
* Type definitions for SDK initialization and configuration
|
|
68
|
+
*/
|
|
69
|
+
/**
|
|
70
|
+
* Default configuration values
|
|
71
|
+
*/
|
|
72
|
+
const DEFAULT_CONFIG = {
|
|
73
|
+
api: {
|
|
74
|
+
baseUrl: 'https://weldsuite-helpdesk-widget.vercel.app',
|
|
75
|
+
widgetId: '',
|
|
76
|
+
timeout: 30000,
|
|
77
|
+
retries: 3,
|
|
78
|
+
},
|
|
79
|
+
iframes: {
|
|
80
|
+
launcher: {
|
|
81
|
+
url: '/widget?mode=launcher',
|
|
82
|
+
name: 'weld-launcher-frame',
|
|
83
|
+
position: { bottom: '24px', right: '24px' },
|
|
84
|
+
size: '60px',
|
|
85
|
+
},
|
|
86
|
+
widget: {
|
|
87
|
+
url: '/widget?mode=widget',
|
|
88
|
+
name: 'weld-widget-frame',
|
|
89
|
+
position: { bottom: '100px', right: '24px' },
|
|
90
|
+
width: '400px',
|
|
91
|
+
height: 'min(680px, calc(100vh - 120px))',
|
|
92
|
+
},
|
|
93
|
+
backdrop: {
|
|
94
|
+
enabled: true,
|
|
95
|
+
closeOnClick: true,
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
customization: {
|
|
99
|
+
primaryColor: '#000000',
|
|
100
|
+
accentColor: '#3b82f6',
|
|
101
|
+
backgroundColor: '#ffffff',
|
|
102
|
+
textColor: '#111827',
|
|
103
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
104
|
+
fontSize: '14px',
|
|
105
|
+
borderRadius: '12px',
|
|
106
|
+
},
|
|
107
|
+
features: {
|
|
108
|
+
attachments: true,
|
|
109
|
+
reactions: true,
|
|
110
|
+
typing: true,
|
|
111
|
+
readReceipts: true,
|
|
112
|
+
offlineMode: false,
|
|
113
|
+
fileUpload: true,
|
|
114
|
+
imageUpload: true,
|
|
115
|
+
voiceMessages: false,
|
|
116
|
+
videoMessages: false,
|
|
117
|
+
},
|
|
118
|
+
mobile: {
|
|
119
|
+
fullScreen: true,
|
|
120
|
+
scrollLock: true,
|
|
121
|
+
keyboardHandling: 'auto',
|
|
122
|
+
safeAreaInsets: true,
|
|
123
|
+
},
|
|
124
|
+
auth: {
|
|
125
|
+
enabled: true,
|
|
126
|
+
mode: 'anonymous',
|
|
127
|
+
},
|
|
128
|
+
locale: {
|
|
129
|
+
locale: 'en',
|
|
130
|
+
dateFormat: 'MMM dd, yyyy',
|
|
131
|
+
timeFormat: 'HH:mm',
|
|
132
|
+
},
|
|
133
|
+
logging: {
|
|
134
|
+
enabled: true,
|
|
135
|
+
level: 'warn',
|
|
136
|
+
prefix: '[Weld]',
|
|
137
|
+
includeTimestamp: true,
|
|
138
|
+
},
|
|
139
|
+
performance: {
|
|
140
|
+
lazyLoad: true,
|
|
141
|
+
preload: false,
|
|
142
|
+
caching: true,
|
|
143
|
+
prefetch: false,
|
|
144
|
+
},
|
|
145
|
+
security: {
|
|
146
|
+
allowedOrigins: [],
|
|
147
|
+
validateMessages: true,
|
|
148
|
+
sanitizeInput: true,
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
/**
|
|
152
|
+
* Configuration validation
|
|
153
|
+
*/
|
|
154
|
+
function validateConfig(config) {
|
|
155
|
+
if (!config.widgetId || typeof config.widgetId !== 'string') {
|
|
156
|
+
throw new Error('WeldConfig: widgetId is required and must be a string');
|
|
157
|
+
}
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Merge configuration with defaults
|
|
162
|
+
*/
|
|
163
|
+
function resolveConfig(config) {
|
|
164
|
+
validateConfig(config);
|
|
165
|
+
return {
|
|
166
|
+
widgetId: config.widgetId,
|
|
167
|
+
api: {
|
|
168
|
+
...DEFAULT_CONFIG.api,
|
|
169
|
+
widgetId: config.widgetId,
|
|
170
|
+
...config.api,
|
|
171
|
+
},
|
|
172
|
+
iframes: {
|
|
173
|
+
launcher: {
|
|
174
|
+
...DEFAULT_CONFIG.iframes.launcher,
|
|
175
|
+
...config.iframes?.launcher,
|
|
176
|
+
position: {
|
|
177
|
+
...DEFAULT_CONFIG.iframes.launcher.position,
|
|
178
|
+
...config.position?.launcher,
|
|
179
|
+
...config.iframes?.launcher?.position,
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
widget: {
|
|
183
|
+
...DEFAULT_CONFIG.iframes.widget,
|
|
184
|
+
...config.iframes?.widget,
|
|
185
|
+
position: {
|
|
186
|
+
...DEFAULT_CONFIG.iframes.widget.position,
|
|
187
|
+
...config.position?.widget,
|
|
188
|
+
...config.iframes?.widget?.position,
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
backdrop: {
|
|
192
|
+
...DEFAULT_CONFIG.iframes.backdrop,
|
|
193
|
+
...config.iframes?.backdrop,
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
customization: {
|
|
197
|
+
...DEFAULT_CONFIG.customization,
|
|
198
|
+
...config.customization,
|
|
199
|
+
},
|
|
200
|
+
features: {
|
|
201
|
+
...DEFAULT_CONFIG.features,
|
|
202
|
+
...config.features,
|
|
203
|
+
},
|
|
204
|
+
mobile: {
|
|
205
|
+
...DEFAULT_CONFIG.mobile,
|
|
206
|
+
...config.mobile,
|
|
207
|
+
},
|
|
208
|
+
auth: {
|
|
209
|
+
...DEFAULT_CONFIG.auth,
|
|
210
|
+
...config.auth,
|
|
211
|
+
},
|
|
212
|
+
locale: {
|
|
213
|
+
...DEFAULT_CONFIG.locale,
|
|
214
|
+
...config.locale,
|
|
215
|
+
},
|
|
216
|
+
logging: {
|
|
217
|
+
...DEFAULT_CONFIG.logging,
|
|
218
|
+
...config.logging,
|
|
219
|
+
},
|
|
220
|
+
performance: {
|
|
221
|
+
...DEFAULT_CONFIG.performance,
|
|
222
|
+
...config.performance,
|
|
223
|
+
},
|
|
224
|
+
security: {
|
|
225
|
+
...DEFAULT_CONFIG.security,
|
|
226
|
+
...config.security,
|
|
227
|
+
},
|
|
228
|
+
// Pass through callbacks
|
|
229
|
+
onReady: config.onReady,
|
|
230
|
+
onError: config.onError,
|
|
231
|
+
onOpen: config.onOpen,
|
|
232
|
+
onClose: config.onClose,
|
|
233
|
+
onMessage: config.onMessage,
|
|
234
|
+
onMinimize: config.onMinimize,
|
|
235
|
+
onMaximize: config.onMaximize,
|
|
236
|
+
onDestroy: config.onDestroy,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Weld SDK - Logger Utility
|
|
242
|
+
* Centralized logging with configurable levels and formatting
|
|
243
|
+
*/
|
|
244
|
+
/**
|
|
245
|
+
* Log levels
|
|
246
|
+
*/
|
|
247
|
+
var LogLevel;
|
|
248
|
+
(function (LogLevel) {
|
|
249
|
+
LogLevel[LogLevel["DEBUG"] = 0] = "DEBUG";
|
|
250
|
+
LogLevel[LogLevel["INFO"] = 1] = "INFO";
|
|
251
|
+
LogLevel[LogLevel["WARN"] = 2] = "WARN";
|
|
252
|
+
LogLevel[LogLevel["ERROR"] = 3] = "ERROR";
|
|
253
|
+
LogLevel[LogLevel["SILENT"] = 4] = "SILENT";
|
|
254
|
+
})(LogLevel || (LogLevel = {}));
|
|
255
|
+
/**
|
|
256
|
+
* Logger class
|
|
257
|
+
*/
|
|
258
|
+
class Logger {
|
|
259
|
+
constructor(config) {
|
|
260
|
+
this.config = {
|
|
261
|
+
enabled: true,
|
|
262
|
+
level: 'warn',
|
|
263
|
+
prefix: '[Weld]',
|
|
264
|
+
includeTimestamp: true,
|
|
265
|
+
...config,
|
|
266
|
+
};
|
|
267
|
+
this.level = this.getLevelFromString(this.config.level);
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Convert string level to LogLevel enum
|
|
271
|
+
*/
|
|
272
|
+
getLevelFromString(level) {
|
|
273
|
+
switch (level.toLowerCase()) {
|
|
274
|
+
case 'debug':
|
|
275
|
+
return LogLevel.DEBUG;
|
|
276
|
+
case 'info':
|
|
277
|
+
return LogLevel.INFO;
|
|
278
|
+
case 'warn':
|
|
279
|
+
return LogLevel.WARN;
|
|
280
|
+
case 'error':
|
|
281
|
+
return LogLevel.ERROR;
|
|
282
|
+
default:
|
|
283
|
+
return LogLevel.WARN;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Check if logging is enabled for level
|
|
288
|
+
*/
|
|
289
|
+
shouldLog(level) {
|
|
290
|
+
return (this.config.enabled ?? true) && level >= this.level;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Format log message
|
|
294
|
+
*/
|
|
295
|
+
format(level, message, _data) {
|
|
296
|
+
const parts = [];
|
|
297
|
+
if (this.config.prefix) {
|
|
298
|
+
parts.push(this.config.prefix);
|
|
299
|
+
}
|
|
300
|
+
if (this.config.includeTimestamp) {
|
|
301
|
+
const timestamp = new Date().toISOString();
|
|
302
|
+
parts.push(`[${timestamp}]`);
|
|
303
|
+
}
|
|
304
|
+
parts.push(`[${level.toUpperCase()}]`);
|
|
305
|
+
parts.push(message);
|
|
306
|
+
return parts;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Log debug message
|
|
310
|
+
*/
|
|
311
|
+
debug(message, data) {
|
|
312
|
+
if (!this.shouldLog(LogLevel.DEBUG))
|
|
313
|
+
return;
|
|
314
|
+
const formatted = this.format('debug', message, data);
|
|
315
|
+
if (data !== undefined) {
|
|
316
|
+
console.debug(...formatted, data);
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
console.debug(...formatted);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Log info message
|
|
324
|
+
*/
|
|
325
|
+
info(message, data) {
|
|
326
|
+
if (!this.shouldLog(LogLevel.INFO))
|
|
327
|
+
return;
|
|
328
|
+
const formatted = this.format('info', message, data);
|
|
329
|
+
if (data !== undefined) {
|
|
330
|
+
console.info(...formatted, data);
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
console.info(...formatted);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Log warning message
|
|
338
|
+
*/
|
|
339
|
+
warn(message, data) {
|
|
340
|
+
if (!this.shouldLog(LogLevel.WARN))
|
|
341
|
+
return;
|
|
342
|
+
const formatted = this.format('warn', message, data);
|
|
343
|
+
if (data !== undefined) {
|
|
344
|
+
console.warn(...formatted, data);
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
console.warn(...formatted);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Log error message
|
|
352
|
+
*/
|
|
353
|
+
error(message, error) {
|
|
354
|
+
if (!this.shouldLog(LogLevel.ERROR))
|
|
355
|
+
return;
|
|
356
|
+
const formatted = this.format('error', message);
|
|
357
|
+
if (error !== undefined) {
|
|
358
|
+
if (error instanceof Error) {
|
|
359
|
+
console.error(...formatted, error.message, error.stack);
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
console.error(...formatted, error);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
console.error(...formatted);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Create child logger with prefix
|
|
371
|
+
*/
|
|
372
|
+
child(prefix) {
|
|
373
|
+
return new Logger({
|
|
374
|
+
...this.config,
|
|
375
|
+
prefix: `${this.config.prefix} ${prefix}`,
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Update log level at runtime
|
|
380
|
+
*/
|
|
381
|
+
setLevel(level) {
|
|
382
|
+
this.config.level = level;
|
|
383
|
+
this.level = this.getLevelFromString(level);
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Enable/disable logging
|
|
387
|
+
*/
|
|
388
|
+
setEnabled(enabled) {
|
|
389
|
+
this.config.enabled = enabled;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Default logger instance
|
|
394
|
+
*/
|
|
395
|
+
new Logger({
|
|
396
|
+
enabled: true,
|
|
397
|
+
level: 'warn',
|
|
398
|
+
prefix: '[Weld]',
|
|
399
|
+
includeTimestamp: true,
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Weld SDK - Iframe Manager
|
|
404
|
+
* Manages creation, lifecycle, and communication with multiple iframes
|
|
405
|
+
*/
|
|
406
|
+
/**
|
|
407
|
+
* Iframe types
|
|
408
|
+
*/
|
|
409
|
+
var IframeType;
|
|
410
|
+
(function (IframeType) {
|
|
411
|
+
IframeType["LAUNCHER"] = "launcher";
|
|
412
|
+
IframeType["WIDGET"] = "widget";
|
|
413
|
+
IframeType["BACKDROP"] = "backdrop";
|
|
414
|
+
})(IframeType || (IframeType = {}));
|
|
415
|
+
/**
|
|
416
|
+
* Device detection result
|
|
417
|
+
*/
|
|
418
|
+
function detectDevice() {
|
|
419
|
+
const width = window.innerWidth;
|
|
420
|
+
const height = window.innerHeight;
|
|
421
|
+
const userAgent = navigator.userAgent;
|
|
422
|
+
const isMobile = width < 768;
|
|
423
|
+
const isTablet = width >= 768 && width <= 1024;
|
|
424
|
+
const isDesktop = width > 1024;
|
|
425
|
+
const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
|
426
|
+
return {
|
|
427
|
+
type: isMobile ? 'mobile' : isTablet ? 'tablet' : 'desktop',
|
|
428
|
+
isMobile,
|
|
429
|
+
isTablet,
|
|
430
|
+
isDesktop,
|
|
431
|
+
isTouchDevice,
|
|
432
|
+
screenWidth: width,
|
|
433
|
+
screenHeight: height,
|
|
434
|
+
orientation: width > height ? 'landscape' : 'portrait',
|
|
435
|
+
userAgent,
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* IframeManager class
|
|
440
|
+
* Orchestrates multiple iframes for the widget system
|
|
441
|
+
*/
|
|
442
|
+
class IframeManager {
|
|
443
|
+
constructor(config) {
|
|
444
|
+
this.iframes = new Map();
|
|
445
|
+
this.rootContainer = null;
|
|
446
|
+
this.appContainer = null;
|
|
447
|
+
this.modalContainer = null;
|
|
448
|
+
this.styleElement = null;
|
|
449
|
+
this.config = config;
|
|
450
|
+
this.logger = new Logger(config.logging);
|
|
451
|
+
this.deviceInfo = detectDevice();
|
|
452
|
+
this.logger.info('IframeManager initialized', {
|
|
453
|
+
device: this.deviceInfo.type,
|
|
454
|
+
mobile: this.deviceInfo.isMobile,
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Initialize all containers and iframes
|
|
459
|
+
*/
|
|
460
|
+
async init() {
|
|
461
|
+
this.logger.debug('Initializing iframe manager');
|
|
462
|
+
try {
|
|
463
|
+
// Create root container structure
|
|
464
|
+
this.createRootContainer();
|
|
465
|
+
// Inject CSS
|
|
466
|
+
this.injectCSS();
|
|
467
|
+
// Create iframes
|
|
468
|
+
await this.createLauncherIframe();
|
|
469
|
+
await this.createBackdropIframe();
|
|
470
|
+
await this.createWidgetIframe();
|
|
471
|
+
// Setup event listeners
|
|
472
|
+
this.setupEventListeners();
|
|
473
|
+
this.logger.info('IframeManager initialized successfully');
|
|
474
|
+
}
|
|
475
|
+
catch (error) {
|
|
476
|
+
this.logger.error('Failed to initialize IframeManager', error);
|
|
477
|
+
throw error;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Create root container structure
|
|
482
|
+
*/
|
|
483
|
+
createRootContainer() {
|
|
484
|
+
// Check if already exists
|
|
485
|
+
let existingContainer = document.getElementById('weld-container');
|
|
486
|
+
if (existingContainer) {
|
|
487
|
+
this.logger.warn('Weld container already exists, removing old instance');
|
|
488
|
+
existingContainer.remove();
|
|
489
|
+
}
|
|
490
|
+
// Create root container
|
|
491
|
+
this.rootContainer = document.createElement('div');
|
|
492
|
+
this.rootContainer.id = 'weld-container';
|
|
493
|
+
this.rootContainer.className = 'weld-namespace';
|
|
494
|
+
// Create app container
|
|
495
|
+
this.appContainer = document.createElement('div');
|
|
496
|
+
this.appContainer.className = 'weld-app';
|
|
497
|
+
this.appContainer.setAttribute('aria-live', 'polite');
|
|
498
|
+
this.appContainer.setAttribute('role', 'complementary');
|
|
499
|
+
this.appContainer.setAttribute('aria-label', 'Weld Helpdesk Widget');
|
|
500
|
+
// Create modal container
|
|
501
|
+
this.modalContainer = document.createElement('div');
|
|
502
|
+
this.modalContainer.id = 'weld-modal-container';
|
|
503
|
+
// Assemble structure
|
|
504
|
+
this.rootContainer.appendChild(this.appContainer);
|
|
505
|
+
this.rootContainer.appendChild(this.modalContainer);
|
|
506
|
+
document.body.appendChild(this.rootContainer);
|
|
507
|
+
this.logger.debug('Root container created');
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Inject CSS into the page
|
|
511
|
+
*/
|
|
512
|
+
injectCSS() {
|
|
513
|
+
// Remove existing style if present
|
|
514
|
+
const existingStyle = document.getElementById('weld-styles');
|
|
515
|
+
if (existingStyle) {
|
|
516
|
+
existingStyle.remove();
|
|
517
|
+
}
|
|
518
|
+
this.styleElement = document.createElement('style');
|
|
519
|
+
this.styleElement.id = 'weld-styles';
|
|
520
|
+
this.styleElement.textContent = this.generateCSS();
|
|
521
|
+
document.head.appendChild(this.styleElement);
|
|
522
|
+
this.logger.debug('CSS injected');
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Generate CSS for containers
|
|
526
|
+
*/
|
|
527
|
+
generateCSS() {
|
|
528
|
+
const { customization } = this.config;
|
|
529
|
+
return `
|
|
530
|
+
/* Weld Container */
|
|
531
|
+
#weld-container {
|
|
532
|
+
--weld-color-primary: ${customization.primaryColor};
|
|
533
|
+
--weld-color-accent: ${customization.accentColor};
|
|
534
|
+
--weld-font-family: ${customization.fontFamily};
|
|
535
|
+
--weld-font-size-base: ${customization.fontSize};
|
|
536
|
+
--weld-radius-xl: ${customization.borderRadius};
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/* Import main stylesheet */
|
|
540
|
+
@import url('/styles/index.css');
|
|
541
|
+
|
|
542
|
+
/* Prevent page scroll when mobile widget is open */
|
|
543
|
+
body.weld-mobile-open {
|
|
544
|
+
overflow: hidden !important;
|
|
545
|
+
position: fixed !important;
|
|
546
|
+
width: 100% !important;
|
|
547
|
+
height: 100% !important;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/* High contrast mode support */
|
|
551
|
+
@media (prefers-contrast: high) {
|
|
552
|
+
.weld-namespace {
|
|
553
|
+
--weld-color-border: currentColor;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
`;
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Create launcher iframe
|
|
560
|
+
*/
|
|
561
|
+
async createLauncherIframe() {
|
|
562
|
+
const { iframes } = this.config;
|
|
563
|
+
const { launcher } = iframes;
|
|
564
|
+
// Create container
|
|
565
|
+
const container = document.createElement('div');
|
|
566
|
+
container.className = 'weld-launcher-frame';
|
|
567
|
+
container.setAttribute('data-state', 'visible');
|
|
568
|
+
container.style.cssText = `
|
|
569
|
+
position: fixed;
|
|
570
|
+
bottom: ${launcher.position.bottom};
|
|
571
|
+
right: ${launcher.position.right};
|
|
572
|
+
width: ${launcher.size};
|
|
573
|
+
height: ${launcher.size};
|
|
574
|
+
z-index: 2147483003;
|
|
575
|
+
pointer-events: auto;
|
|
576
|
+
display: block;
|
|
577
|
+
`;
|
|
578
|
+
// Create iframe
|
|
579
|
+
const iframe = document.createElement('iframe');
|
|
580
|
+
iframe.name = launcher.name;
|
|
581
|
+
iframe.title = 'Weld Launcher';
|
|
582
|
+
iframe.src = this.buildIframeUrl(launcher.url);
|
|
583
|
+
iframe.style.cssText = `
|
|
584
|
+
width: 100%;
|
|
585
|
+
height: 100%;
|
|
586
|
+
border: none;
|
|
587
|
+
background: transparent;
|
|
588
|
+
display: block;
|
|
589
|
+
`;
|
|
590
|
+
iframe.setAttribute('allow', 'clipboard-write');
|
|
591
|
+
iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-popups');
|
|
592
|
+
container.appendChild(iframe);
|
|
593
|
+
this.appContainer?.appendChild(container);
|
|
594
|
+
// Store metadata
|
|
595
|
+
this.iframes.set(IframeType.LAUNCHER, {
|
|
596
|
+
type: IframeType.LAUNCHER,
|
|
597
|
+
element: iframe,
|
|
598
|
+
container,
|
|
599
|
+
ready: false,
|
|
600
|
+
visible: true,
|
|
601
|
+
createdAt: Date.now(),
|
|
602
|
+
});
|
|
603
|
+
// Mark as ready when loaded
|
|
604
|
+
iframe.onload = () => {
|
|
605
|
+
const metadata = this.iframes.get(IframeType.LAUNCHER);
|
|
606
|
+
if (metadata) {
|
|
607
|
+
metadata.ready = true;
|
|
608
|
+
this.logger.debug('Launcher iframe loaded and ready');
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
this.logger.debug('Launcher iframe created');
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Create widget iframe
|
|
615
|
+
*/
|
|
616
|
+
async createWidgetIframe() {
|
|
617
|
+
const { iframes } = this.config;
|
|
618
|
+
const { widget } = iframes;
|
|
619
|
+
// Create container
|
|
620
|
+
const container = document.createElement('div');
|
|
621
|
+
container.className = 'weld-widget-frame';
|
|
622
|
+
container.setAttribute('data-state', 'closed');
|
|
623
|
+
container.style.cssText = `
|
|
624
|
+
position: fixed;
|
|
625
|
+
bottom: ${widget.position.bottom};
|
|
626
|
+
right: ${widget.position.right};
|
|
627
|
+
width: ${widget.width};
|
|
628
|
+
height: ${widget.height};
|
|
629
|
+
max-width: 100vw;
|
|
630
|
+
z-index: 2147483001;
|
|
631
|
+
pointer-events: none;
|
|
632
|
+
display: none;
|
|
633
|
+
border-radius: 16px;
|
|
634
|
+
overflow: hidden;
|
|
635
|
+
background: transparent;
|
|
636
|
+
`;
|
|
637
|
+
// Create iframe
|
|
638
|
+
const iframe = document.createElement('iframe');
|
|
639
|
+
iframe.name = widget.name;
|
|
640
|
+
iframe.title = 'Weld Widget';
|
|
641
|
+
iframe.src = this.buildIframeUrl(widget.url);
|
|
642
|
+
iframe.style.cssText = `
|
|
643
|
+
width: 100%;
|
|
644
|
+
height: 100%;
|
|
645
|
+
border: none;
|
|
646
|
+
background: transparent;
|
|
647
|
+
display: block;
|
|
648
|
+
border-radius: 16px;
|
|
649
|
+
`;
|
|
650
|
+
iframe.setAttribute('allow', 'clipboard-write; camera; microphone');
|
|
651
|
+
iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-popups allow-downloads');
|
|
652
|
+
container.appendChild(iframe);
|
|
653
|
+
this.appContainer?.appendChild(container);
|
|
654
|
+
// Store metadata
|
|
655
|
+
this.iframes.set(IframeType.WIDGET, {
|
|
656
|
+
type: IframeType.WIDGET,
|
|
657
|
+
element: iframe,
|
|
658
|
+
container,
|
|
659
|
+
ready: false,
|
|
660
|
+
visible: false,
|
|
661
|
+
createdAt: Date.now(),
|
|
662
|
+
});
|
|
663
|
+
// Mark as ready when loaded
|
|
664
|
+
iframe.onload = () => {
|
|
665
|
+
const metadata = this.iframes.get(IframeType.WIDGET);
|
|
666
|
+
if (metadata) {
|
|
667
|
+
metadata.ready = true;
|
|
668
|
+
this.logger.debug('Widget iframe loaded and ready');
|
|
669
|
+
}
|
|
670
|
+
};
|
|
671
|
+
this.logger.debug('Widget iframe created');
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Create backdrop iframe
|
|
675
|
+
*/
|
|
676
|
+
async createBackdropIframe() {
|
|
677
|
+
if (!this.config.iframes.backdrop?.enabled) {
|
|
678
|
+
this.logger.debug('Backdrop disabled, skipping creation');
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
// Create container
|
|
682
|
+
const container = document.createElement('div');
|
|
683
|
+
container.className = 'weld-backdrop-frame';
|
|
684
|
+
container.setAttribute('data-state', 'hidden');
|
|
685
|
+
container.style.cssText = `
|
|
686
|
+
position: fixed;
|
|
687
|
+
top: 0;
|
|
688
|
+
left: 0;
|
|
689
|
+
right: 0;
|
|
690
|
+
bottom: 0;
|
|
691
|
+
z-index: 2147483000;
|
|
692
|
+
background: transparent;
|
|
693
|
+
pointer-events: none;
|
|
694
|
+
opacity: 0;
|
|
695
|
+
transition: opacity 200ms ease;
|
|
696
|
+
`;
|
|
697
|
+
this.appContainer?.appendChild(container);
|
|
698
|
+
// Store metadata (backdrop doesn't have an iframe, just a div)
|
|
699
|
+
// We'll create a minimal "iframe" reference for consistency
|
|
700
|
+
const dummyIframe = document.createElement('iframe');
|
|
701
|
+
dummyIframe.style.display = 'none';
|
|
702
|
+
this.iframes.set(IframeType.BACKDROP, {
|
|
703
|
+
type: IframeType.BACKDROP,
|
|
704
|
+
element: dummyIframe,
|
|
705
|
+
container,
|
|
706
|
+
ready: true, // Backdrop is always ready
|
|
707
|
+
visible: false,
|
|
708
|
+
createdAt: Date.now(),
|
|
709
|
+
});
|
|
710
|
+
this.logger.debug('Backdrop created');
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Build iframe URL with parameters
|
|
714
|
+
*/
|
|
715
|
+
buildIframeUrl(path) {
|
|
716
|
+
const { widgetId, api } = this.config;
|
|
717
|
+
const baseUrl = api.baseUrl;
|
|
718
|
+
// Handle paths that may already have query parameters
|
|
719
|
+
const url = new URL(path, baseUrl);
|
|
720
|
+
url.searchParams.set('widgetId', widgetId);
|
|
721
|
+
url.searchParams.set('device', this.deviceInfo.type);
|
|
722
|
+
url.searchParams.set('mobile', String(this.deviceInfo.isMobile));
|
|
723
|
+
url.searchParams.set('parentOrigin', window.location.origin);
|
|
724
|
+
return url.toString();
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Setup event listeners
|
|
728
|
+
*/
|
|
729
|
+
setupEventListeners() {
|
|
730
|
+
// Window resize
|
|
731
|
+
window.addEventListener('resize', this.handleResize.bind(this));
|
|
732
|
+
// Orientation change
|
|
733
|
+
window.addEventListener('orientationchange', this.handleOrientationChange.bind(this));
|
|
734
|
+
this.logger.debug('Event listeners setup');
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Handle window resize
|
|
738
|
+
*/
|
|
739
|
+
handleResize() {
|
|
740
|
+
this.deviceInfo = detectDevice();
|
|
741
|
+
this.logger.debug('Window resized', { device: this.deviceInfo.type });
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Handle orientation change
|
|
745
|
+
*/
|
|
746
|
+
handleOrientationChange() {
|
|
747
|
+
this.deviceInfo = detectDevice();
|
|
748
|
+
this.logger.debug('Orientation changed', { orientation: this.deviceInfo.orientation });
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Get iframe by type
|
|
752
|
+
*/
|
|
753
|
+
getIframe(type) {
|
|
754
|
+
return this.iframes.get(type);
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Get iframe element
|
|
758
|
+
*/
|
|
759
|
+
getIframeElement(type) {
|
|
760
|
+
return this.iframes.get(type)?.element;
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* Get iframe container
|
|
764
|
+
*/
|
|
765
|
+
getIframeContainer(type) {
|
|
766
|
+
return this.iframes.get(type)?.container;
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Mark iframe as ready
|
|
770
|
+
*/
|
|
771
|
+
setIframeReady(type) {
|
|
772
|
+
const iframe = this.iframes.get(type);
|
|
773
|
+
if (iframe) {
|
|
774
|
+
iframe.ready = true;
|
|
775
|
+
this.logger.debug(`Iframe ${type} marked as ready`);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Check if all iframes are ready
|
|
780
|
+
*/
|
|
781
|
+
areAllIframesReady() {
|
|
782
|
+
for (const [type, iframe] of this.iframes) {
|
|
783
|
+
if (type !== IframeType.BACKDROP && !iframe.ready) {
|
|
784
|
+
return false;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
return true;
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Show iframe
|
|
791
|
+
*/
|
|
792
|
+
showIframe(type) {
|
|
793
|
+
const iframe = this.iframes.get(type);
|
|
794
|
+
if (!iframe) {
|
|
795
|
+
console.warn(`[Weld SDK] showIframe: iframe ${type} not found`);
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
console.log(`[Weld SDK] Showing iframe ${type}`, { currentDisplay: iframe.container.style.display });
|
|
799
|
+
iframe.visible = true;
|
|
800
|
+
iframe.container.setAttribute('data-state', type === IframeType.BACKDROP ? 'visible' : 'open');
|
|
801
|
+
iframe.container.style.pointerEvents = 'auto';
|
|
802
|
+
iframe.container.style.display = 'block';
|
|
803
|
+
// Handle mobile scroll lock
|
|
804
|
+
if (this.deviceInfo.isMobile && type === IframeType.WIDGET && this.config.mobile.scrollLock) {
|
|
805
|
+
document.body.classList.add('weld-mobile-open');
|
|
806
|
+
}
|
|
807
|
+
console.log(`[Weld SDK] Iframe ${type} shown`, { newDisplay: iframe.container.style.display });
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Hide iframe
|
|
811
|
+
*/
|
|
812
|
+
hideIframe(type) {
|
|
813
|
+
const iframe = this.iframes.get(type);
|
|
814
|
+
if (!iframe) {
|
|
815
|
+
console.warn(`[Weld SDK] hideIframe: iframe ${type} not found`);
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
console.log(`[Weld SDK] Hiding iframe ${type}`, { currentDisplay: iframe.container.style.display });
|
|
819
|
+
iframe.visible = false;
|
|
820
|
+
iframe.container.setAttribute('data-state', type === IframeType.BACKDROP ? 'hidden' : 'closed');
|
|
821
|
+
iframe.container.style.pointerEvents = 'none';
|
|
822
|
+
iframe.container.style.display = 'none';
|
|
823
|
+
// Remove mobile scroll lock
|
|
824
|
+
if (this.deviceInfo.isMobile && type === IframeType.WIDGET) {
|
|
825
|
+
document.body.classList.remove('weld-mobile-open');
|
|
826
|
+
}
|
|
827
|
+
console.log(`[Weld SDK] Iframe ${type} hidden`, { newDisplay: iframe.container.style.display });
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Get device info
|
|
831
|
+
*/
|
|
832
|
+
getDeviceInfo() {
|
|
833
|
+
return this.deviceInfo;
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Get modal container
|
|
837
|
+
*/
|
|
838
|
+
getModalContainer() {
|
|
839
|
+
return this.modalContainer;
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* Destroy all iframes and cleanup
|
|
843
|
+
*/
|
|
844
|
+
destroy() {
|
|
845
|
+
this.logger.debug('Destroying iframe manager');
|
|
846
|
+
// Remove event listeners
|
|
847
|
+
window.removeEventListener('resize', this.handleResize.bind(this));
|
|
848
|
+
window.removeEventListener('orientationchange', this.handleOrientationChange.bind(this));
|
|
849
|
+
// Remove mobile scroll lock
|
|
850
|
+
document.body.classList.remove('weld-mobile-open');
|
|
851
|
+
// Remove root container
|
|
852
|
+
if (this.rootContainer) {
|
|
853
|
+
this.rootContainer.remove();
|
|
854
|
+
this.rootContainer = null;
|
|
855
|
+
}
|
|
856
|
+
// Remove style element
|
|
857
|
+
if (this.styleElement) {
|
|
858
|
+
this.styleElement.remove();
|
|
859
|
+
this.styleElement = null;
|
|
860
|
+
}
|
|
861
|
+
// Clear iframe references
|
|
862
|
+
this.iframes.clear();
|
|
863
|
+
this.logger.info('IframeManager destroyed');
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
/**
|
|
868
|
+
* Weld SDK - Message Types
|
|
869
|
+
* Type definitions for postMessage communication between parent and iframes
|
|
870
|
+
*/
|
|
871
|
+
/**
|
|
872
|
+
* Message origins for validation
|
|
873
|
+
*/
|
|
874
|
+
var MessageOrigin;
|
|
875
|
+
(function (MessageOrigin) {
|
|
876
|
+
MessageOrigin["LAUNCHER"] = "launcher";
|
|
877
|
+
MessageOrigin["WIDGET"] = "widget";
|
|
878
|
+
MessageOrigin["PARENT"] = "parent";
|
|
879
|
+
MessageOrigin["BACKDROP"] = "backdrop";
|
|
880
|
+
})(MessageOrigin || (MessageOrigin = {}));
|
|
881
|
+
/**
|
|
882
|
+
* Message types for different communication patterns
|
|
883
|
+
*/
|
|
884
|
+
var MessageType;
|
|
885
|
+
(function (MessageType) {
|
|
886
|
+
// Lifecycle
|
|
887
|
+
MessageType["READY"] = "weld:ready";
|
|
888
|
+
MessageType["INIT"] = "weld:init";
|
|
889
|
+
MessageType["DESTROY"] = "weld:destroy";
|
|
890
|
+
// State changes
|
|
891
|
+
MessageType["STATE_UPDATE"] = "weld:state:update";
|
|
892
|
+
MessageType["STATE_REQUEST"] = "weld:state:request";
|
|
893
|
+
MessageType["STATE_RESPONSE"] = "weld:state:response";
|
|
894
|
+
// Widget control
|
|
895
|
+
MessageType["WIDGET_OPEN"] = "weld:widget:open";
|
|
896
|
+
MessageType["WIDGET_CLOSE"] = "weld:widget:close";
|
|
897
|
+
MessageType["WIDGET_TOGGLE"] = "weld:widget:toggle";
|
|
898
|
+
MessageType["WIDGET_MINIMIZE"] = "weld:widget:minimize";
|
|
899
|
+
MessageType["WIDGET_MAXIMIZE"] = "weld:widget:maximize";
|
|
900
|
+
// Launcher control
|
|
901
|
+
MessageType["LAUNCHER_SHOW"] = "weld:launcher:show";
|
|
902
|
+
MessageType["LAUNCHER_HIDE"] = "weld:launcher:hide";
|
|
903
|
+
MessageType["LAUNCHER_UPDATE"] = "weld:launcher:update";
|
|
904
|
+
// Backdrop control
|
|
905
|
+
MessageType["BACKDROP_SHOW"] = "weld:backdrop:show";
|
|
906
|
+
MessageType["BACKDROP_HIDE"] = "weld:backdrop:hide";
|
|
907
|
+
MessageType["BACKDROP_CLICK"] = "weld:backdrop:click";
|
|
908
|
+
// User interactions
|
|
909
|
+
MessageType["MESSAGE_SEND"] = "weld:message:send";
|
|
910
|
+
MessageType["MESSAGE_RECEIVE"] = "weld:message:receive";
|
|
911
|
+
MessageType["TYPING_START"] = "weld:typing:start";
|
|
912
|
+
MessageType["TYPING_STOP"] = "weld:typing:stop";
|
|
913
|
+
// Badge updates
|
|
914
|
+
MessageType["BADGE_UPDATE"] = "weld:badge:update";
|
|
915
|
+
MessageType["BADGE_CLEAR"] = "weld:badge:clear";
|
|
916
|
+
// Mobile handling
|
|
917
|
+
MessageType["MOBILE_SCROLL_LOCK"] = "weld:mobile:scroll:lock";
|
|
918
|
+
MessageType["MOBILE_SCROLL_UNLOCK"] = "weld:mobile:scroll:unlock";
|
|
919
|
+
// Configuration
|
|
920
|
+
MessageType["CONFIG_UPDATE"] = "weld:config:update";
|
|
921
|
+
MessageType["THEME_UPDATE"] = "weld:theme:update";
|
|
922
|
+
MessageType["LOCALE_UPDATE"] = "weld:locale:update";
|
|
923
|
+
// Authentication
|
|
924
|
+
MessageType["AUTH_LOGIN"] = "weld:auth:login";
|
|
925
|
+
MessageType["AUTH_LOGOUT"] = "weld:auth:logout";
|
|
926
|
+
MessageType["AUTH_TOKEN_UPDATE"] = "weld:auth:token:update";
|
|
927
|
+
// Events
|
|
928
|
+
MessageType["EVENT_TRACK"] = "weld:event:track";
|
|
929
|
+
MessageType["ERROR_REPORT"] = "weld:error:report";
|
|
930
|
+
// API responses
|
|
931
|
+
MessageType["API_SUCCESS"] = "weld:api:success";
|
|
932
|
+
MessageType["API_ERROR"] = "weld:api:error";
|
|
933
|
+
})(MessageType || (MessageType = {}));
|
|
934
|
+
/**
|
|
935
|
+
* Type guards
|
|
936
|
+
*/
|
|
937
|
+
function isBaseMessage(message) {
|
|
938
|
+
return (typeof message === 'object' &&
|
|
939
|
+
message !== null &&
|
|
940
|
+
'type' in message &&
|
|
941
|
+
'origin' in message &&
|
|
942
|
+
'timestamp' in message &&
|
|
943
|
+
'id' in message);
|
|
944
|
+
}
|
|
945
|
+
function isPayloadMessage(message) {
|
|
946
|
+
return isBaseMessage(message) && 'payload' in message;
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* Message creator utilities
|
|
950
|
+
*/
|
|
951
|
+
function createMessage(type, origin, payload) {
|
|
952
|
+
return {
|
|
953
|
+
type,
|
|
954
|
+
origin,
|
|
955
|
+
timestamp: Date.now(),
|
|
956
|
+
id: `${type}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
957
|
+
payload,
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
/**
|
|
962
|
+
* Weld SDK - Security Utilities
|
|
963
|
+
* Security validation and origin checking for postMessage communication
|
|
964
|
+
*/
|
|
965
|
+
/**
|
|
966
|
+
* SecurityManager class
|
|
967
|
+
*/
|
|
968
|
+
class SecurityManager {
|
|
969
|
+
constructor(config, logger) {
|
|
970
|
+
this.config = config;
|
|
971
|
+
this.logger = logger;
|
|
972
|
+
this.allowedOrigins = new Set(config.allowedOrigins || []);
|
|
973
|
+
// Always allow same origin
|
|
974
|
+
this.allowedOrigins.add(window.location.origin);
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* Validate message origin
|
|
978
|
+
*/
|
|
979
|
+
isOriginAllowed(origin) {
|
|
980
|
+
// If no allowed origins specified, only allow same origin
|
|
981
|
+
if (this.config.allowedOrigins?.length === 0) {
|
|
982
|
+
return origin === window.location.origin;
|
|
983
|
+
}
|
|
984
|
+
// Check if origin is in allowed list
|
|
985
|
+
if (this.allowedOrigins.has(origin)) {
|
|
986
|
+
return true;
|
|
987
|
+
}
|
|
988
|
+
// Check for wildcard patterns
|
|
989
|
+
for (const allowed of this.allowedOrigins) {
|
|
990
|
+
if (this.matchesPattern(origin, allowed)) {
|
|
991
|
+
return true;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
this.logger.warn('Origin not allowed', { origin });
|
|
995
|
+
return false;
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Match origin against pattern (supports wildcards)
|
|
999
|
+
*/
|
|
1000
|
+
matchesPattern(origin, pattern) {
|
|
1001
|
+
// Exact match
|
|
1002
|
+
if (origin === pattern) {
|
|
1003
|
+
return true;
|
|
1004
|
+
}
|
|
1005
|
+
// Wildcard pattern (e.g., "https://*.example.com")
|
|
1006
|
+
if (pattern.includes('*')) {
|
|
1007
|
+
const regex = new RegExp('^' + pattern.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$');
|
|
1008
|
+
return regex.test(origin);
|
|
1009
|
+
}
|
|
1010
|
+
return false;
|
|
1011
|
+
}
|
|
1012
|
+
/**
|
|
1013
|
+
* Validate postMessage event
|
|
1014
|
+
*/
|
|
1015
|
+
validateMessageEvent(event) {
|
|
1016
|
+
// Check origin
|
|
1017
|
+
if (!this.isOriginAllowed(event.origin)) {
|
|
1018
|
+
this.logger.warn('Invalid message origin', { origin: event.origin });
|
|
1019
|
+
return false;
|
|
1020
|
+
}
|
|
1021
|
+
// Check if message has data
|
|
1022
|
+
if (!event.data) {
|
|
1023
|
+
this.logger.warn('Message has no data');
|
|
1024
|
+
return false;
|
|
1025
|
+
}
|
|
1026
|
+
// Check if message is an object
|
|
1027
|
+
if (typeof event.data !== 'object') {
|
|
1028
|
+
this.logger.warn('Message data is not an object');
|
|
1029
|
+
return false;
|
|
1030
|
+
}
|
|
1031
|
+
return true;
|
|
1032
|
+
}
|
|
1033
|
+
/**
|
|
1034
|
+
* Sanitize message data
|
|
1035
|
+
*/
|
|
1036
|
+
sanitizeMessageData(data) {
|
|
1037
|
+
if (!this.config.sanitizeInput) {
|
|
1038
|
+
return data;
|
|
1039
|
+
}
|
|
1040
|
+
// Deep clone to avoid modifying original
|
|
1041
|
+
const sanitized = JSON.parse(JSON.stringify(data));
|
|
1042
|
+
// Recursively sanitize strings
|
|
1043
|
+
this.sanitizeObject(sanitized);
|
|
1044
|
+
return sanitized;
|
|
1045
|
+
}
|
|
1046
|
+
/**
|
|
1047
|
+
* Recursively sanitize object properties
|
|
1048
|
+
*/
|
|
1049
|
+
sanitizeObject(obj) {
|
|
1050
|
+
if (typeof obj !== 'object' || obj === null) {
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
for (const key in obj) {
|
|
1054
|
+
if (typeof obj[key] === 'string') {
|
|
1055
|
+
obj[key] = this.sanitizeString(obj[key]);
|
|
1056
|
+
}
|
|
1057
|
+
else if (typeof obj[key] === 'object') {
|
|
1058
|
+
this.sanitizeObject(obj[key]);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
/**
|
|
1063
|
+
* Sanitize string to prevent XSS
|
|
1064
|
+
*/
|
|
1065
|
+
sanitizeString(str) {
|
|
1066
|
+
const div = document.createElement('div');
|
|
1067
|
+
div.textContent = str;
|
|
1068
|
+
return div.innerHTML
|
|
1069
|
+
.replace(/javascript:/gi, '')
|
|
1070
|
+
.replace(/on\w+\s*=/gi, '');
|
|
1071
|
+
}
|
|
1072
|
+
/**
|
|
1073
|
+
* Generate secure random ID
|
|
1074
|
+
*/
|
|
1075
|
+
generateSecureId() {
|
|
1076
|
+
const array = new Uint8Array(16);
|
|
1077
|
+
crypto.getRandomValues(array);
|
|
1078
|
+
return Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
1079
|
+
}
|
|
1080
|
+
/**
|
|
1081
|
+
* Validate iframe source
|
|
1082
|
+
*/
|
|
1083
|
+
isValidIframeSource(src) {
|
|
1084
|
+
try {
|
|
1085
|
+
const url = new URL(src, window.location.origin);
|
|
1086
|
+
// Check if URL is from allowed origin
|
|
1087
|
+
if (!this.isOriginAllowed(url.origin)) {
|
|
1088
|
+
this.logger.warn('Invalid iframe source origin', { src });
|
|
1089
|
+
return false;
|
|
1090
|
+
}
|
|
1091
|
+
// Prevent javascript: protocol
|
|
1092
|
+
if (url.protocol === 'javascript:') {
|
|
1093
|
+
this.logger.warn('Javascript protocol not allowed in iframe source');
|
|
1094
|
+
return false;
|
|
1095
|
+
}
|
|
1096
|
+
return true;
|
|
1097
|
+
}
|
|
1098
|
+
catch (error) {
|
|
1099
|
+
this.logger.warn('Invalid iframe source URL', { src, error });
|
|
1100
|
+
return false;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
/**
|
|
1104
|
+
* Create Content Security Policy
|
|
1105
|
+
*/
|
|
1106
|
+
createCSP() {
|
|
1107
|
+
const origins = Array.from(this.allowedOrigins).join(' ');
|
|
1108
|
+
return [
|
|
1109
|
+
`default-src 'self' ${origins}`,
|
|
1110
|
+
`script-src 'self' 'unsafe-inline' ${origins}`,
|
|
1111
|
+
`style-src 'self' 'unsafe-inline' ${origins}`,
|
|
1112
|
+
`img-src 'self' data: https: ${origins}`,
|
|
1113
|
+
`font-src 'self' data: ${origins}`,
|
|
1114
|
+
`connect-src 'self' ${origins}`,
|
|
1115
|
+
`frame-src 'self' ${origins}`,
|
|
1116
|
+
`media-src 'self' ${origins}`,
|
|
1117
|
+
].join('; ');
|
|
1118
|
+
}
|
|
1119
|
+
/**
|
|
1120
|
+
* Add allowed origin
|
|
1121
|
+
*/
|
|
1122
|
+
addAllowedOrigin(origin) {
|
|
1123
|
+
this.allowedOrigins.add(origin);
|
|
1124
|
+
this.logger.debug('Added allowed origin', { origin });
|
|
1125
|
+
}
|
|
1126
|
+
/**
|
|
1127
|
+
* Remove allowed origin
|
|
1128
|
+
*/
|
|
1129
|
+
removeAllowedOrigin(origin) {
|
|
1130
|
+
this.allowedOrigins.delete(origin);
|
|
1131
|
+
this.logger.debug('Removed allowed origin', { origin });
|
|
1132
|
+
}
|
|
1133
|
+
/**
|
|
1134
|
+
* Get all allowed origins
|
|
1135
|
+
*/
|
|
1136
|
+
getAllowedOrigins() {
|
|
1137
|
+
return Array.from(this.allowedOrigins);
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
/**
|
|
1141
|
+
* Rate limiting utility
|
|
1142
|
+
*/
|
|
1143
|
+
class RateLimiter {
|
|
1144
|
+
constructor(maxRequests = 100, windowMs = 60000) {
|
|
1145
|
+
this.requests = new Map();
|
|
1146
|
+
this.maxRequests = maxRequests;
|
|
1147
|
+
this.windowMs = windowMs;
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* Check if request is allowed
|
|
1151
|
+
*/
|
|
1152
|
+
isAllowed(key) {
|
|
1153
|
+
const now = Date.now();
|
|
1154
|
+
const requests = this.requests.get(key) || [];
|
|
1155
|
+
// Remove old requests outside window
|
|
1156
|
+
const validRequests = requests.filter((time) => now - time < this.windowMs);
|
|
1157
|
+
// Check if limit exceeded
|
|
1158
|
+
if (validRequests.length >= this.maxRequests) {
|
|
1159
|
+
return false;
|
|
1160
|
+
}
|
|
1161
|
+
// Add current request
|
|
1162
|
+
validRequests.push(now);
|
|
1163
|
+
this.requests.set(key, validRequests);
|
|
1164
|
+
return true;
|
|
1165
|
+
}
|
|
1166
|
+
/**
|
|
1167
|
+
* Reset rate limit for key
|
|
1168
|
+
*/
|
|
1169
|
+
reset(key) {
|
|
1170
|
+
this.requests.delete(key);
|
|
1171
|
+
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Clear all rate limits
|
|
1174
|
+
*/
|
|
1175
|
+
clearAll() {
|
|
1176
|
+
this.requests.clear();
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
/**
|
|
1181
|
+
* Weld SDK - Message Broker
|
|
1182
|
+
* Handles secure postMessage communication between parent and iframes
|
|
1183
|
+
*/
|
|
1184
|
+
/**
|
|
1185
|
+
* MessageBroker class
|
|
1186
|
+
* Central hub for all postMessage communication
|
|
1187
|
+
*/
|
|
1188
|
+
class MessageBroker {
|
|
1189
|
+
constructor(config, iframeManager, logger) {
|
|
1190
|
+
this.subscriptions = new Map();
|
|
1191
|
+
this.messageQueue = [];
|
|
1192
|
+
this.isReady = false;
|
|
1193
|
+
this.responseHandlers = new Map();
|
|
1194
|
+
this.config = config;
|
|
1195
|
+
this.logger = logger.child('[MessageBroker]');
|
|
1196
|
+
this.iframeManager = iframeManager;
|
|
1197
|
+
this.security = new SecurityManager(config.security, this.logger);
|
|
1198
|
+
this.rateLimiter = new RateLimiter(100, 60000); // 100 messages per minute
|
|
1199
|
+
this.setupMessageListener();
|
|
1200
|
+
this.logger.debug('MessageBroker initialized');
|
|
1201
|
+
}
|
|
1202
|
+
/**
|
|
1203
|
+
* Setup global message listener
|
|
1204
|
+
*/
|
|
1205
|
+
setupMessageListener() {
|
|
1206
|
+
window.addEventListener('message', this.handleMessage.bind(this));
|
|
1207
|
+
this.logger.debug('Message listener setup');
|
|
1208
|
+
}
|
|
1209
|
+
/**
|
|
1210
|
+
* Handle incoming postMessage
|
|
1211
|
+
*/
|
|
1212
|
+
handleMessage(event) {
|
|
1213
|
+
// Validate message event
|
|
1214
|
+
if (!this.security.validateMessageEvent(event)) {
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
// Validate message structure
|
|
1218
|
+
if (!isBaseMessage(event.data)) {
|
|
1219
|
+
this.logger.warn('Invalid message structure', event.data);
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1222
|
+
const message = event.data;
|
|
1223
|
+
// Rate limiting
|
|
1224
|
+
if (!this.rateLimiter.isAllowed(message.origin)) {
|
|
1225
|
+
this.logger.warn('Rate limit exceeded', { origin: message.origin });
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
// Sanitize message if enabled
|
|
1229
|
+
const sanitized = this.config.security.sanitizeInput
|
|
1230
|
+
? this.security.sanitizeMessageData(message)
|
|
1231
|
+
: message;
|
|
1232
|
+
this.logger.debug('Message received', {
|
|
1233
|
+
type: sanitized.type,
|
|
1234
|
+
origin: sanitized.origin,
|
|
1235
|
+
id: sanitized.id,
|
|
1236
|
+
});
|
|
1237
|
+
// Check for response handlers
|
|
1238
|
+
if (this.responseHandlers.has(sanitized.id)) {
|
|
1239
|
+
const handler = this.responseHandlers.get(sanitized.id);
|
|
1240
|
+
handler?.(sanitized);
|
|
1241
|
+
this.responseHandlers.delete(sanitized.id);
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1244
|
+
// Dispatch to subscribers
|
|
1245
|
+
this.dispatchMessage(sanitized);
|
|
1246
|
+
}
|
|
1247
|
+
/**
|
|
1248
|
+
* Dispatch message to subscribers
|
|
1249
|
+
*/
|
|
1250
|
+
dispatchMessage(message) {
|
|
1251
|
+
const subscriptions = Array.from(this.subscriptions.values());
|
|
1252
|
+
for (const subscription of subscriptions) {
|
|
1253
|
+
// Check if type matches
|
|
1254
|
+
const typeMatches = subscription.type === '*' || subscription.type === message.type;
|
|
1255
|
+
// Check if origin matches (if specified)
|
|
1256
|
+
const originMatches = !subscription.origin || subscription.origin === message.origin;
|
|
1257
|
+
if (typeMatches && originMatches) {
|
|
1258
|
+
try {
|
|
1259
|
+
if (isPayloadMessage(message)) {
|
|
1260
|
+
subscription.handler(message.payload, message);
|
|
1261
|
+
}
|
|
1262
|
+
else {
|
|
1263
|
+
subscription.handler(undefined, message);
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
catch (error) {
|
|
1267
|
+
this.logger.error('Error in message handler', error);
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
/**
|
|
1273
|
+
* Subscribe to messages
|
|
1274
|
+
*/
|
|
1275
|
+
subscribe(type, handler, origin) {
|
|
1276
|
+
const id = this.security.generateSecureId();
|
|
1277
|
+
this.subscriptions.set(id, {
|
|
1278
|
+
id,
|
|
1279
|
+
type,
|
|
1280
|
+
origin,
|
|
1281
|
+
handler,
|
|
1282
|
+
});
|
|
1283
|
+
this.logger.debug('Subscribed to messages', { type, origin, id });
|
|
1284
|
+
return id;
|
|
1285
|
+
}
|
|
1286
|
+
/**
|
|
1287
|
+
* Unsubscribe from messages
|
|
1288
|
+
*/
|
|
1289
|
+
unsubscribe(subscriptionId) {
|
|
1290
|
+
if (this.subscriptions.delete(subscriptionId)) {
|
|
1291
|
+
this.logger.debug('Unsubscribed from messages', { id: subscriptionId });
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
/**
|
|
1295
|
+
* Send message to iframe
|
|
1296
|
+
*/
|
|
1297
|
+
sendToIframe(iframeType, type, payload) {
|
|
1298
|
+
const iframe = this.iframeManager.getIframe(iframeType);
|
|
1299
|
+
if (!iframe) {
|
|
1300
|
+
this.logger.warn('Iframe not found', { iframeType });
|
|
1301
|
+
return;
|
|
1302
|
+
}
|
|
1303
|
+
if (!iframe.ready && type !== 'weld:init') {
|
|
1304
|
+
// Queue message if iframe not ready
|
|
1305
|
+
const message = createMessage(type, MessageOrigin.PARENT, payload);
|
|
1306
|
+
this.messageQueue.push(message);
|
|
1307
|
+
this.logger.debug('Message queued (iframe not ready)', {
|
|
1308
|
+
iframeType,
|
|
1309
|
+
type,
|
|
1310
|
+
});
|
|
1311
|
+
return;
|
|
1312
|
+
}
|
|
1313
|
+
const message = createMessage(type, MessageOrigin.PARENT, payload);
|
|
1314
|
+
this.postMessage(iframe.element, message);
|
|
1315
|
+
}
|
|
1316
|
+
/**
|
|
1317
|
+
* Send message to all iframes
|
|
1318
|
+
*/
|
|
1319
|
+
broadcast(type, payload) {
|
|
1320
|
+
const message = createMessage(type, MessageOrigin.PARENT, payload);
|
|
1321
|
+
const launcherIframe = this.iframeManager.getIframe(IframeType.LAUNCHER);
|
|
1322
|
+
const widgetIframe = this.iframeManager.getIframe(IframeType.WIDGET);
|
|
1323
|
+
if (launcherIframe && launcherIframe.ready) {
|
|
1324
|
+
this.postMessage(launcherIframe.element, message);
|
|
1325
|
+
}
|
|
1326
|
+
if (widgetIframe && widgetIframe.ready) {
|
|
1327
|
+
this.postMessage(widgetIframe.element, message);
|
|
1328
|
+
}
|
|
1329
|
+
this.logger.debug('Message broadcast', { type });
|
|
1330
|
+
}
|
|
1331
|
+
/**
|
|
1332
|
+
* Send message and wait for response
|
|
1333
|
+
*/
|
|
1334
|
+
async sendAndWaitForResponse(iframeType, type, payload, timeout = 5000) {
|
|
1335
|
+
return new Promise((resolve, reject) => {
|
|
1336
|
+
const message = createMessage(type, MessageOrigin.PARENT, payload);
|
|
1337
|
+
// Setup response handler
|
|
1338
|
+
const timeoutId = setTimeout(() => {
|
|
1339
|
+
this.responseHandlers.delete(message.id);
|
|
1340
|
+
reject(new Error('Message response timeout'));
|
|
1341
|
+
}, timeout);
|
|
1342
|
+
this.responseHandlers.set(message.id, (response) => {
|
|
1343
|
+
clearTimeout(timeoutId);
|
|
1344
|
+
if (isPayloadMessage(response)) {
|
|
1345
|
+
resolve(response.payload);
|
|
1346
|
+
}
|
|
1347
|
+
else {
|
|
1348
|
+
resolve(undefined);
|
|
1349
|
+
}
|
|
1350
|
+
});
|
|
1351
|
+
// Send message
|
|
1352
|
+
const iframe = this.iframeManager.getIframe(iframeType);
|
|
1353
|
+
if (!iframe) {
|
|
1354
|
+
reject(new Error('Iframe not found'));
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1357
|
+
this.postMessage(iframe.element, message);
|
|
1358
|
+
});
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1361
|
+
* Post message to iframe
|
|
1362
|
+
*/
|
|
1363
|
+
postMessage(iframe, message) {
|
|
1364
|
+
if (!iframe.contentWindow) {
|
|
1365
|
+
this.logger.warn('Iframe contentWindow not available');
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
try {
|
|
1369
|
+
iframe.contentWindow.postMessage(message, '*');
|
|
1370
|
+
this.logger.debug('Message sent', {
|
|
1371
|
+
type: message.type,
|
|
1372
|
+
id: message.id,
|
|
1373
|
+
});
|
|
1374
|
+
}
|
|
1375
|
+
catch (error) {
|
|
1376
|
+
this.logger.error('Failed to post message', error);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
/**
|
|
1380
|
+
* Mark iframe as ready and flush queued messages
|
|
1381
|
+
*/
|
|
1382
|
+
setIframeReady(iframeType) {
|
|
1383
|
+
this.iframeManager.setIframeReady(iframeType);
|
|
1384
|
+
// Flush queued messages for this iframe
|
|
1385
|
+
this.flushMessageQueue(iframeType);
|
|
1386
|
+
// Check if all iframes ready
|
|
1387
|
+
if (this.iframeManager.areAllIframesReady()) {
|
|
1388
|
+
this.isReady = true;
|
|
1389
|
+
this.logger.info('All iframes ready');
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
/**
|
|
1393
|
+
* Flush queued messages to iframe
|
|
1394
|
+
*/
|
|
1395
|
+
flushMessageQueue(iframeType) {
|
|
1396
|
+
const iframe = this.iframeManager.getIframe(iframeType);
|
|
1397
|
+
if (!iframe)
|
|
1398
|
+
return;
|
|
1399
|
+
const queuedMessages = this.messageQueue.splice(0);
|
|
1400
|
+
for (const message of queuedMessages) {
|
|
1401
|
+
this.postMessage(iframe.element, message);
|
|
1402
|
+
}
|
|
1403
|
+
if (queuedMessages.length > 0) {
|
|
1404
|
+
this.logger.debug('Flushed queued messages', {
|
|
1405
|
+
count: queuedMessages.length,
|
|
1406
|
+
iframeType,
|
|
1407
|
+
});
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
/**
|
|
1411
|
+
* Check if broker is ready
|
|
1412
|
+
*/
|
|
1413
|
+
isMessageBrokerReady() {
|
|
1414
|
+
return this.isReady;
|
|
1415
|
+
}
|
|
1416
|
+
/**
|
|
1417
|
+
* Get queued message count
|
|
1418
|
+
*/
|
|
1419
|
+
getQueuedMessageCount() {
|
|
1420
|
+
return this.messageQueue.length;
|
|
1421
|
+
}
|
|
1422
|
+
/**
|
|
1423
|
+
* Clear message queue
|
|
1424
|
+
*/
|
|
1425
|
+
clearMessageQueue() {
|
|
1426
|
+
this.messageQueue = [];
|
|
1427
|
+
this.logger.debug('Message queue cleared');
|
|
1428
|
+
}
|
|
1429
|
+
/**
|
|
1430
|
+
* Get active subscription count
|
|
1431
|
+
*/
|
|
1432
|
+
getSubscriptionCount() {
|
|
1433
|
+
return this.subscriptions.size;
|
|
1434
|
+
}
|
|
1435
|
+
/**
|
|
1436
|
+
* Destroy broker and cleanup
|
|
1437
|
+
*/
|
|
1438
|
+
destroy() {
|
|
1439
|
+
this.logger.debug('Destroying message broker');
|
|
1440
|
+
// Remove event listener
|
|
1441
|
+
window.removeEventListener('message', this.handleMessage.bind(this));
|
|
1442
|
+
// Clear subscriptions
|
|
1443
|
+
this.subscriptions.clear();
|
|
1444
|
+
// Clear message queue
|
|
1445
|
+
this.messageQueue = [];
|
|
1446
|
+
// Clear response handlers
|
|
1447
|
+
this.responseHandlers.clear();
|
|
1448
|
+
// Reset rate limiter
|
|
1449
|
+
this.rateLimiter.clearAll();
|
|
1450
|
+
this.isReady = false;
|
|
1451
|
+
this.logger.info('MessageBroker destroyed');
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
/**
|
|
1456
|
+
* Weld SDK - State Types
|
|
1457
|
+
* Type definitions for state management across iframes
|
|
1458
|
+
*/
|
|
1459
|
+
/**
|
|
1460
|
+
* Widget visibility state
|
|
1461
|
+
*/
|
|
1462
|
+
var WidgetVisibility;
|
|
1463
|
+
(function (WidgetVisibility) {
|
|
1464
|
+
WidgetVisibility["HIDDEN"] = "hidden";
|
|
1465
|
+
WidgetVisibility["VISIBLE"] = "visible";
|
|
1466
|
+
WidgetVisibility["MINIMIZED"] = "minimized";
|
|
1467
|
+
})(WidgetVisibility || (WidgetVisibility = {}));
|
|
1468
|
+
/**
|
|
1469
|
+
* Widget view types
|
|
1470
|
+
*/
|
|
1471
|
+
var WidgetView;
|
|
1472
|
+
(function (WidgetView) {
|
|
1473
|
+
WidgetView["HOME"] = "home";
|
|
1474
|
+
WidgetView["CONVERSATION"] = "conversation";
|
|
1475
|
+
WidgetView["CONVERSATIONS"] = "conversations";
|
|
1476
|
+
WidgetView["HELP"] = "help";
|
|
1477
|
+
WidgetView["SETTINGS"] = "settings";
|
|
1478
|
+
WidgetView["SEARCH"] = "search";
|
|
1479
|
+
})(WidgetView || (WidgetView = {}));
|
|
1480
|
+
/**
|
|
1481
|
+
* Connection status
|
|
1482
|
+
*/
|
|
1483
|
+
var ConnectionStatus;
|
|
1484
|
+
(function (ConnectionStatus) {
|
|
1485
|
+
ConnectionStatus["DISCONNECTED"] = "disconnected";
|
|
1486
|
+
ConnectionStatus["CONNECTING"] = "connecting";
|
|
1487
|
+
ConnectionStatus["CONNECTED"] = "connected";
|
|
1488
|
+
ConnectionStatus["RECONNECTING"] = "reconnecting";
|
|
1489
|
+
ConnectionStatus["ERROR"] = "error";
|
|
1490
|
+
})(ConnectionStatus || (ConnectionStatus = {}));
|
|
1491
|
+
/**
|
|
1492
|
+
* Message status
|
|
1493
|
+
*/
|
|
1494
|
+
var MessageStatus;
|
|
1495
|
+
(function (MessageStatus) {
|
|
1496
|
+
MessageStatus["SENDING"] = "sending";
|
|
1497
|
+
MessageStatus["SENT"] = "sent";
|
|
1498
|
+
MessageStatus["DELIVERED"] = "delivered";
|
|
1499
|
+
MessageStatus["READ"] = "read";
|
|
1500
|
+
MessageStatus["FAILED"] = "failed";
|
|
1501
|
+
})(MessageStatus || (MessageStatus = {}));
|
|
1502
|
+
/**
|
|
1503
|
+
* Initial state factory
|
|
1504
|
+
*/
|
|
1505
|
+
function createInitialState() {
|
|
1506
|
+
return {
|
|
1507
|
+
user: {
|
|
1508
|
+
isAuthenticated: false,
|
|
1509
|
+
isAnonymous: true,
|
|
1510
|
+
},
|
|
1511
|
+
conversation: {
|
|
1512
|
+
messages: [],
|
|
1513
|
+
participants: [],
|
|
1514
|
+
unreadCount: 0,
|
|
1515
|
+
isTyping: false,
|
|
1516
|
+
typingUsers: [],
|
|
1517
|
+
},
|
|
1518
|
+
widget: {
|
|
1519
|
+
visibility: WidgetVisibility.HIDDEN,
|
|
1520
|
+
view: WidgetView.HOME,
|
|
1521
|
+
isOpen: false,
|
|
1522
|
+
isMinimized: false,
|
|
1523
|
+
dimensions: {
|
|
1524
|
+
width: '400px',
|
|
1525
|
+
height: 'min(680px, 88vh)',
|
|
1526
|
+
},
|
|
1527
|
+
position: {
|
|
1528
|
+
bottom: '24px',
|
|
1529
|
+
right: '24px',
|
|
1530
|
+
},
|
|
1531
|
+
},
|
|
1532
|
+
launcher: {
|
|
1533
|
+
isVisible: true,
|
|
1534
|
+
badge: {
|
|
1535
|
+
count: 0,
|
|
1536
|
+
show: false,
|
|
1537
|
+
},
|
|
1538
|
+
},
|
|
1539
|
+
backdrop: {
|
|
1540
|
+
isVisible: false,
|
|
1541
|
+
closeOnClick: true,
|
|
1542
|
+
opacity: 0,
|
|
1543
|
+
},
|
|
1544
|
+
mobile: {
|
|
1545
|
+
isFullScreen: false,
|
|
1546
|
+
isScrollLocked: false,
|
|
1547
|
+
keyboardHeight: 0,
|
|
1548
|
+
orientation: 'portrait',
|
|
1549
|
+
safeAreaInsets: {
|
|
1550
|
+
top: 0,
|
|
1551
|
+
right: 0,
|
|
1552
|
+
bottom: 0,
|
|
1553
|
+
left: 0,
|
|
1554
|
+
},
|
|
1555
|
+
},
|
|
1556
|
+
network: {
|
|
1557
|
+
status: ConnectionStatus.DISCONNECTED,
|
|
1558
|
+
retryCount: 0,
|
|
1559
|
+
},
|
|
1560
|
+
ui: {
|
|
1561
|
+
theme: 'auto',
|
|
1562
|
+
locale: 'en',
|
|
1563
|
+
isLoading: false,
|
|
1564
|
+
},
|
|
1565
|
+
initialized: false,
|
|
1566
|
+
lastUpdated: Date.now(),
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
/**
|
|
1570
|
+
* State path utility
|
|
1571
|
+
*/
|
|
1572
|
+
function getStateValue(state, path) {
|
|
1573
|
+
const keys = path.split('.');
|
|
1574
|
+
let value = state;
|
|
1575
|
+
for (const key of keys) {
|
|
1576
|
+
if (value && typeof value === 'object' && key in value) {
|
|
1577
|
+
value = value[key];
|
|
1578
|
+
}
|
|
1579
|
+
else {
|
|
1580
|
+
return undefined;
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
return value;
|
|
1584
|
+
}
|
|
1585
|
+
/**
|
|
1586
|
+
* State update utility
|
|
1587
|
+
*/
|
|
1588
|
+
function setStateValue(state, path, value, merge = false) {
|
|
1589
|
+
const keys = path.split('.');
|
|
1590
|
+
const newState = JSON.parse(JSON.stringify(state));
|
|
1591
|
+
let current = newState;
|
|
1592
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
1593
|
+
const key = keys[i];
|
|
1594
|
+
if (!(key in current)) {
|
|
1595
|
+
current[key] = {};
|
|
1596
|
+
}
|
|
1597
|
+
current = current[key];
|
|
1598
|
+
}
|
|
1599
|
+
const lastKey = keys[keys.length - 1];
|
|
1600
|
+
if (merge && typeof current[lastKey] === 'object' && typeof value === 'object') {
|
|
1601
|
+
current[lastKey] = { ...current[lastKey], ...value };
|
|
1602
|
+
}
|
|
1603
|
+
else {
|
|
1604
|
+
current[lastKey] = value;
|
|
1605
|
+
}
|
|
1606
|
+
newState.lastUpdated = Date.now();
|
|
1607
|
+
return newState;
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
/**
|
|
1611
|
+
* Weld SDK - Validation Utilities
|
|
1612
|
+
* Input validation and sanitization functions
|
|
1613
|
+
*/
|
|
1614
|
+
/**
|
|
1615
|
+
* Validate email format
|
|
1616
|
+
*/
|
|
1617
|
+
/**
|
|
1618
|
+
* Deep clone object (safe for JSON-serializable objects)
|
|
1619
|
+
*/
|
|
1620
|
+
function deepClone(obj) {
|
|
1621
|
+
if (obj === null || typeof obj !== 'object') {
|
|
1622
|
+
return obj;
|
|
1623
|
+
}
|
|
1624
|
+
if (obj instanceof Date) {
|
|
1625
|
+
return new Date(obj.getTime());
|
|
1626
|
+
}
|
|
1627
|
+
if (obj instanceof Array) {
|
|
1628
|
+
return obj.map((item) => deepClone(item));
|
|
1629
|
+
}
|
|
1630
|
+
if (obj instanceof Object) {
|
|
1631
|
+
const cloned = {};
|
|
1632
|
+
for (const key in obj) {
|
|
1633
|
+
if (obj.hasOwnProperty(key)) {
|
|
1634
|
+
cloned[key] = deepClone(obj[key]);
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
return cloned;
|
|
1638
|
+
}
|
|
1639
|
+
return obj;
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
/**
|
|
1643
|
+
* Weld SDK - State Coordinator
|
|
1644
|
+
* Manages and synchronizes state across iframes
|
|
1645
|
+
*/
|
|
1646
|
+
/**
|
|
1647
|
+
* StateCoordinator class
|
|
1648
|
+
* Central state management for multi-iframe architecture
|
|
1649
|
+
*/
|
|
1650
|
+
class StateCoordinator {
|
|
1651
|
+
constructor(messageBroker, logger) {
|
|
1652
|
+
this.subscriptions = new Map();
|
|
1653
|
+
this.stateHistory = [];
|
|
1654
|
+
this.maxHistorySize = 50;
|
|
1655
|
+
this.messageBroker = messageBroker;
|
|
1656
|
+
this.logger = logger.child('[StateCoordinator]');
|
|
1657
|
+
this.state = createInitialState();
|
|
1658
|
+
this.setupMessageHandlers();
|
|
1659
|
+
this.logger.debug('StateCoordinator initialized');
|
|
1660
|
+
}
|
|
1661
|
+
/**
|
|
1662
|
+
* Setup message handlers for state updates
|
|
1663
|
+
*/
|
|
1664
|
+
setupMessageHandlers() {
|
|
1665
|
+
// Listen for state update requests from iframes
|
|
1666
|
+
this.messageBroker.subscribe('weld:state:update', this.handleStateUpdate.bind(this));
|
|
1667
|
+
// Listen for state request messages
|
|
1668
|
+
this.messageBroker.subscribe('weld:state:request', this.handleStateRequest.bind(this));
|
|
1669
|
+
this.logger.debug('Message handlers setup');
|
|
1670
|
+
}
|
|
1671
|
+
/**
|
|
1672
|
+
* Handle state update from iframe
|
|
1673
|
+
*/
|
|
1674
|
+
handleStateUpdate(payload) {
|
|
1675
|
+
const { path, value, merge } = payload;
|
|
1676
|
+
if (!path) {
|
|
1677
|
+
this.logger.warn('State update missing path', payload);
|
|
1678
|
+
return;
|
|
1679
|
+
}
|
|
1680
|
+
this.updateState(path, value, merge);
|
|
1681
|
+
}
|
|
1682
|
+
/**
|
|
1683
|
+
* Handle state request from iframe
|
|
1684
|
+
*/
|
|
1685
|
+
handleStateRequest(payload) {
|
|
1686
|
+
const { path } = payload;
|
|
1687
|
+
if (path) {
|
|
1688
|
+
const value = getStateValue(this.state, path);
|
|
1689
|
+
this.messageBroker.broadcast('weld:state:response', {
|
|
1690
|
+
path,
|
|
1691
|
+
value,
|
|
1692
|
+
});
|
|
1693
|
+
}
|
|
1694
|
+
else {
|
|
1695
|
+
// Send entire state
|
|
1696
|
+
this.messageBroker.broadcast('weld:state:response', {
|
|
1697
|
+
state: this.state,
|
|
1698
|
+
});
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
/**
|
|
1702
|
+
* Get current state
|
|
1703
|
+
*/
|
|
1704
|
+
getState() {
|
|
1705
|
+
return deepClone(this.state);
|
|
1706
|
+
}
|
|
1707
|
+
/**
|
|
1708
|
+
* Get state value by path
|
|
1709
|
+
*/
|
|
1710
|
+
getValue(path) {
|
|
1711
|
+
return getStateValue(this.state, path);
|
|
1712
|
+
}
|
|
1713
|
+
/**
|
|
1714
|
+
* Update state
|
|
1715
|
+
*/
|
|
1716
|
+
updateState(path, value, merge = false) {
|
|
1717
|
+
const oldState = deepClone(this.state);
|
|
1718
|
+
this.state = setStateValue(this.state, path, value, merge);
|
|
1719
|
+
// Create state action
|
|
1720
|
+
const action = {
|
|
1721
|
+
type: 'UPDATE',
|
|
1722
|
+
path,
|
|
1723
|
+
payload: value,
|
|
1724
|
+
merge,
|
|
1725
|
+
timestamp: Date.now(),
|
|
1726
|
+
};
|
|
1727
|
+
// Add to history
|
|
1728
|
+
this.addToHistory(action);
|
|
1729
|
+
// Notify subscribers
|
|
1730
|
+
this.notifySubscribers(path, value, getStateValue(oldState, path));
|
|
1731
|
+
// Broadcast to iframes
|
|
1732
|
+
this.messageBroker.broadcast('weld:state:update', {
|
|
1733
|
+
path,
|
|
1734
|
+
value,
|
|
1735
|
+
merge,
|
|
1736
|
+
});
|
|
1737
|
+
this.logger.debug('State updated', { path, merge });
|
|
1738
|
+
}
|
|
1739
|
+
/**
|
|
1740
|
+
* Batch update multiple state paths
|
|
1741
|
+
*/
|
|
1742
|
+
batchUpdate(updates) {
|
|
1743
|
+
const oldState = deepClone(this.state);
|
|
1744
|
+
for (const update of updates) {
|
|
1745
|
+
this.state = setStateValue(this.state, update.path, update.value, update.merge);
|
|
1746
|
+
}
|
|
1747
|
+
// Notify subscribers for each update
|
|
1748
|
+
for (const update of updates) {
|
|
1749
|
+
this.notifySubscribers(update.path, getStateValue(this.state, update.path), getStateValue(oldState, update.path));
|
|
1750
|
+
}
|
|
1751
|
+
// Broadcast all updates
|
|
1752
|
+
this.messageBroker.broadcast('weld:state:update', {
|
|
1753
|
+
batch: updates,
|
|
1754
|
+
});
|
|
1755
|
+
this.logger.debug('Batch state update', { count: updates.length });
|
|
1756
|
+
}
|
|
1757
|
+
/**
|
|
1758
|
+
* Reset state to initial
|
|
1759
|
+
*/
|
|
1760
|
+
resetState() {
|
|
1761
|
+
const oldState = this.state;
|
|
1762
|
+
this.state = createInitialState();
|
|
1763
|
+
// Notify all subscribers
|
|
1764
|
+
for (const [_id, subscription] of this.subscriptions) {
|
|
1765
|
+
const oldValue = getStateValue(oldState, subscription.path);
|
|
1766
|
+
const newValue = getStateValue(this.state, subscription.path);
|
|
1767
|
+
subscription.listener(newValue, oldValue);
|
|
1768
|
+
}
|
|
1769
|
+
// Broadcast reset
|
|
1770
|
+
this.messageBroker.broadcast('weld:state:update', {
|
|
1771
|
+
reset: true,
|
|
1772
|
+
state: this.state,
|
|
1773
|
+
});
|
|
1774
|
+
this.logger.debug('State reset');
|
|
1775
|
+
}
|
|
1776
|
+
/**
|
|
1777
|
+
* Subscribe to state changes
|
|
1778
|
+
*/
|
|
1779
|
+
subscribe(path, listener, immediate = false) {
|
|
1780
|
+
const id = `sub_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
1781
|
+
this.subscriptions.set(id, {
|
|
1782
|
+
id,
|
|
1783
|
+
path,
|
|
1784
|
+
listener,
|
|
1785
|
+
immediate,
|
|
1786
|
+
});
|
|
1787
|
+
// Call listener immediately if requested
|
|
1788
|
+
if (immediate) {
|
|
1789
|
+
const value = getStateValue(this.state, path);
|
|
1790
|
+
listener(value, value);
|
|
1791
|
+
}
|
|
1792
|
+
this.logger.debug('State subscription added', { id, path });
|
|
1793
|
+
return id;
|
|
1794
|
+
}
|
|
1795
|
+
/**
|
|
1796
|
+
* Unsubscribe from state changes
|
|
1797
|
+
*/
|
|
1798
|
+
unsubscribe(subscriptionId) {
|
|
1799
|
+
if (this.subscriptions.delete(subscriptionId)) {
|
|
1800
|
+
this.logger.debug('State subscription removed', { id: subscriptionId });
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
/**
|
|
1804
|
+
* Notify subscribers of state change
|
|
1805
|
+
*/
|
|
1806
|
+
notifySubscribers(path, newValue, oldValue) {
|
|
1807
|
+
const subscriptions = Array.from(this.subscriptions.values());
|
|
1808
|
+
for (const subscription of subscriptions) {
|
|
1809
|
+
// Exact path match
|
|
1810
|
+
if (subscription.path === path) {
|
|
1811
|
+
try {
|
|
1812
|
+
subscription.listener(newValue, oldValue);
|
|
1813
|
+
}
|
|
1814
|
+
catch (error) {
|
|
1815
|
+
this.logger.error('Error in state listener', error);
|
|
1816
|
+
}
|
|
1817
|
+
continue;
|
|
1818
|
+
}
|
|
1819
|
+
// Parent path match (e.g., subscriber to "user" gets notified for "user.name")
|
|
1820
|
+
if (path.startsWith(subscription.path + '.')) {
|
|
1821
|
+
const subValue = getStateValue(this.state, subscription.path);
|
|
1822
|
+
try {
|
|
1823
|
+
subscription.listener(subValue, oldValue);
|
|
1824
|
+
}
|
|
1825
|
+
catch (error) {
|
|
1826
|
+
this.logger.error('Error in state listener', error);
|
|
1827
|
+
}
|
|
1828
|
+
continue;
|
|
1829
|
+
}
|
|
1830
|
+
// Child path match (e.g., subscriber to "user.name" gets notified for "user")
|
|
1831
|
+
if (subscription.path.startsWith(path + '.')) {
|
|
1832
|
+
const subValue = getStateValue(this.state, subscription.path);
|
|
1833
|
+
try {
|
|
1834
|
+
subscription.listener(subValue, undefined);
|
|
1835
|
+
}
|
|
1836
|
+
catch (error) {
|
|
1837
|
+
this.logger.error('Error in state listener', error);
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
/**
|
|
1843
|
+
* Add action to history
|
|
1844
|
+
*/
|
|
1845
|
+
addToHistory(action) {
|
|
1846
|
+
this.stateHistory.push(action);
|
|
1847
|
+
// Trim history if too large
|
|
1848
|
+
if (this.stateHistory.length > this.maxHistorySize) {
|
|
1849
|
+
this.stateHistory.shift();
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
/**
|
|
1853
|
+
* Get state history
|
|
1854
|
+
*/
|
|
1855
|
+
getHistory() {
|
|
1856
|
+
return [...this.stateHistory];
|
|
1857
|
+
}
|
|
1858
|
+
/**
|
|
1859
|
+
* Clear state history
|
|
1860
|
+
*/
|
|
1861
|
+
clearHistory() {
|
|
1862
|
+
this.stateHistory = [];
|
|
1863
|
+
this.logger.debug('State history cleared');
|
|
1864
|
+
}
|
|
1865
|
+
/**
|
|
1866
|
+
* Get snapshot of state at specific time
|
|
1867
|
+
*/
|
|
1868
|
+
getSnapshot(timestamp) {
|
|
1869
|
+
// Find all actions before timestamp
|
|
1870
|
+
const actions = this.stateHistory.filter((a) => a.timestamp <= timestamp);
|
|
1871
|
+
if (actions.length === 0) {
|
|
1872
|
+
return null;
|
|
1873
|
+
}
|
|
1874
|
+
// Replay actions to reconstruct state
|
|
1875
|
+
let state = createInitialState();
|
|
1876
|
+
for (const action of actions) {
|
|
1877
|
+
state = setStateValue(state, action.path, action.payload, action.merge);
|
|
1878
|
+
}
|
|
1879
|
+
return state;
|
|
1880
|
+
}
|
|
1881
|
+
/**
|
|
1882
|
+
* Widget-specific state helpers
|
|
1883
|
+
*/
|
|
1884
|
+
openWidget() {
|
|
1885
|
+
this.batchUpdate([
|
|
1886
|
+
{ path: 'widget.isOpen', value: true },
|
|
1887
|
+
{ path: 'widget.visibility', value: 'visible' },
|
|
1888
|
+
{ path: 'launcher.isVisible', value: false },
|
|
1889
|
+
{ path: 'backdrop.isVisible', value: true },
|
|
1890
|
+
]);
|
|
1891
|
+
}
|
|
1892
|
+
closeWidget() {
|
|
1893
|
+
this.batchUpdate([
|
|
1894
|
+
{ path: 'widget.isOpen', value: false },
|
|
1895
|
+
{ path: 'widget.visibility', value: 'hidden' },
|
|
1896
|
+
{ path: 'launcher.isVisible', value: true },
|
|
1897
|
+
{ path: 'backdrop.isVisible', value: false },
|
|
1898
|
+
]);
|
|
1899
|
+
}
|
|
1900
|
+
minimizeWidget() {
|
|
1901
|
+
this.batchUpdate([
|
|
1902
|
+
{ path: 'widget.isMinimized', value: true },
|
|
1903
|
+
{ path: 'widget.visibility', value: 'minimized' },
|
|
1904
|
+
]);
|
|
1905
|
+
}
|
|
1906
|
+
maximizeWidget() {
|
|
1907
|
+
this.batchUpdate([
|
|
1908
|
+
{ path: 'widget.isMinimized', value: false },
|
|
1909
|
+
{ path: 'widget.visibility', value: 'visible' },
|
|
1910
|
+
]);
|
|
1911
|
+
}
|
|
1912
|
+
setBadgeCount(count) {
|
|
1913
|
+
this.batchUpdate([
|
|
1914
|
+
{ path: 'launcher.badge.count', value: count },
|
|
1915
|
+
{ path: 'launcher.badge.show', value: count > 0 },
|
|
1916
|
+
]);
|
|
1917
|
+
}
|
|
1918
|
+
setUserAuth(userId, email, name) {
|
|
1919
|
+
this.batchUpdate([
|
|
1920
|
+
{ path: 'user.isAuthenticated', value: true },
|
|
1921
|
+
{ path: 'user.isAnonymous', value: false },
|
|
1922
|
+
{ path: 'user.id', value: userId },
|
|
1923
|
+
{ path: 'user.email', value: email },
|
|
1924
|
+
{ path: 'user.name', value: name },
|
|
1925
|
+
]);
|
|
1926
|
+
}
|
|
1927
|
+
setConnectionStatus(status) {
|
|
1928
|
+
this.updateState('network.status', status);
|
|
1929
|
+
}
|
|
1930
|
+
setLoading(isLoading) {
|
|
1931
|
+
this.updateState('ui.isLoading', isLoading);
|
|
1932
|
+
}
|
|
1933
|
+
setError(code, message, recoverable = true) {
|
|
1934
|
+
this.updateState('ui.error', { code, message, recoverable });
|
|
1935
|
+
}
|
|
1936
|
+
clearError() {
|
|
1937
|
+
this.updateState('ui.error', undefined);
|
|
1938
|
+
}
|
|
1939
|
+
/**
|
|
1940
|
+
* Get active subscription count
|
|
1941
|
+
*/
|
|
1942
|
+
getSubscriptionCount() {
|
|
1943
|
+
return this.subscriptions.size;
|
|
1944
|
+
}
|
|
1945
|
+
/**
|
|
1946
|
+
* Validate state integrity
|
|
1947
|
+
*/
|
|
1948
|
+
validateState() {
|
|
1949
|
+
try {
|
|
1950
|
+
// Check required properties exist
|
|
1951
|
+
const requiredPaths = [
|
|
1952
|
+
'user',
|
|
1953
|
+
'conversation',
|
|
1954
|
+
'widget',
|
|
1955
|
+
'launcher',
|
|
1956
|
+
'backdrop',
|
|
1957
|
+
'mobile',
|
|
1958
|
+
'network',
|
|
1959
|
+
'ui',
|
|
1960
|
+
];
|
|
1961
|
+
for (const path of requiredPaths) {
|
|
1962
|
+
if (getStateValue(this.state, path) === undefined) {
|
|
1963
|
+
this.logger.error('Missing required state path', { path });
|
|
1964
|
+
return false;
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
return true;
|
|
1968
|
+
}
|
|
1969
|
+
catch (error) {
|
|
1970
|
+
this.logger.error('State validation failed', error);
|
|
1971
|
+
return false;
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
/**
|
|
1975
|
+
* Export state as JSON
|
|
1976
|
+
*/
|
|
1977
|
+
exportState() {
|
|
1978
|
+
return JSON.stringify(this.state, null, 2);
|
|
1979
|
+
}
|
|
1980
|
+
/**
|
|
1981
|
+
* Import state from JSON
|
|
1982
|
+
*/
|
|
1983
|
+
importState(json) {
|
|
1984
|
+
try {
|
|
1985
|
+
const imported = JSON.parse(json);
|
|
1986
|
+
this.state = { ...createInitialState(), ...imported };
|
|
1987
|
+
this.messageBroker.broadcast('weld:state:update', {
|
|
1988
|
+
reset: true,
|
|
1989
|
+
state: this.state,
|
|
1990
|
+
});
|
|
1991
|
+
this.logger.info('State imported');
|
|
1992
|
+
return true;
|
|
1993
|
+
}
|
|
1994
|
+
catch (error) {
|
|
1995
|
+
this.logger.error('Failed to import state', error);
|
|
1996
|
+
return false;
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
/**
|
|
2000
|
+
* Destroy coordinator and cleanup
|
|
2001
|
+
*/
|
|
2002
|
+
destroy() {
|
|
2003
|
+
this.logger.debug('Destroying state coordinator');
|
|
2004
|
+
// Clear subscriptions
|
|
2005
|
+
this.subscriptions.clear();
|
|
2006
|
+
// Clear history
|
|
2007
|
+
this.stateHistory = [];
|
|
2008
|
+
// Reset state
|
|
2009
|
+
this.state = createInitialState();
|
|
2010
|
+
this.logger.info('StateCoordinator destroyed');
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
var version = "1.0.2";
|
|
2015
|
+
var packageJson = {
|
|
2016
|
+
version: version};
|
|
2017
|
+
|
|
2018
|
+
/**
|
|
2019
|
+
* Weld SDK - Main Entry Point
|
|
2020
|
+
* Public API for the Weld helpdesk widget
|
|
2021
|
+
*/
|
|
2022
|
+
/**
|
|
2023
|
+
* SDK initialization status
|
|
2024
|
+
*/
|
|
2025
|
+
var SDKStatus;
|
|
2026
|
+
(function (SDKStatus) {
|
|
2027
|
+
SDKStatus["UNINITIALIZED"] = "uninitialized";
|
|
2028
|
+
SDKStatus["INITIALIZING"] = "initializing";
|
|
2029
|
+
SDKStatus["READY"] = "ready";
|
|
2030
|
+
SDKStatus["ERROR"] = "error";
|
|
2031
|
+
SDKStatus["DESTROYED"] = "destroyed";
|
|
2032
|
+
})(SDKStatus || (SDKStatus = {}));
|
|
2033
|
+
/**
|
|
2034
|
+
* WeldSDK class
|
|
2035
|
+
* Main SDK interface for embedding the widget
|
|
2036
|
+
*/
|
|
2037
|
+
class WeldSDK {
|
|
2038
|
+
constructor(config) {
|
|
2039
|
+
this.status = SDKStatus.UNINITIALIZED;
|
|
2040
|
+
this.readyPromise = null;
|
|
2041
|
+
this.readyResolve = null;
|
|
2042
|
+
// Resolve configuration
|
|
2043
|
+
this.config = resolveConfig(config);
|
|
2044
|
+
// Initialize logger
|
|
2045
|
+
this.logger = new Logger(this.config.logging);
|
|
2046
|
+
this.logger.info('WeldSDK created', {
|
|
2047
|
+
widgetId: this.config.widgetId,
|
|
2048
|
+
});
|
|
2049
|
+
// Create ready promise
|
|
2050
|
+
this.readyPromise = new Promise((resolve) => {
|
|
2051
|
+
this.readyResolve = resolve;
|
|
2052
|
+
});
|
|
2053
|
+
// Initialize managers
|
|
2054
|
+
this.iframeManager = new IframeManager(this.config);
|
|
2055
|
+
this.messageBroker = new MessageBroker(this.config, this.iframeManager, this.logger);
|
|
2056
|
+
this.stateCoordinator = new StateCoordinator(this.messageBroker, this.logger);
|
|
2057
|
+
}
|
|
2058
|
+
/**
|
|
2059
|
+
* Initialize the SDK and render widget
|
|
2060
|
+
*/
|
|
2061
|
+
async init() {
|
|
2062
|
+
if (this.status !== SDKStatus.UNINITIALIZED) {
|
|
2063
|
+
this.logger.warn('SDK already initialized');
|
|
2064
|
+
return this.readyPromise;
|
|
2065
|
+
}
|
|
2066
|
+
this.status = SDKStatus.INITIALIZING;
|
|
2067
|
+
this.logger.info('Initializing WeldSDK');
|
|
2068
|
+
try {
|
|
2069
|
+
// Initialize iframe manager
|
|
2070
|
+
await this.iframeManager.init();
|
|
2071
|
+
// Setup ready handlers
|
|
2072
|
+
this.setupReadyHandlers();
|
|
2073
|
+
// Wait for all iframes to be ready
|
|
2074
|
+
await this.waitForIframesReady();
|
|
2075
|
+
// Mark as ready
|
|
2076
|
+
this.status = SDKStatus.READY;
|
|
2077
|
+
this.readyResolve?.();
|
|
2078
|
+
this.logger.info('WeldSDK ready');
|
|
2079
|
+
// Call onReady callback
|
|
2080
|
+
this.config.onReady?.();
|
|
2081
|
+
}
|
|
2082
|
+
catch (error) {
|
|
2083
|
+
this.status = SDKStatus.ERROR;
|
|
2084
|
+
this.logger.error('Failed to initialize WeldSDK', error);
|
|
2085
|
+
this.config.onError?.(error);
|
|
2086
|
+
throw error;
|
|
2087
|
+
}
|
|
2088
|
+
return this.readyPromise;
|
|
2089
|
+
}
|
|
2090
|
+
/**
|
|
2091
|
+
* Setup ready message handlers
|
|
2092
|
+
*/
|
|
2093
|
+
setupReadyHandlers() {
|
|
2094
|
+
this.messageBroker.subscribe('weld:ready', (payload) => {
|
|
2095
|
+
const { iframe } = payload;
|
|
2096
|
+
this.logger.debug('Iframe ready', { iframe });
|
|
2097
|
+
// Map iframe name to type
|
|
2098
|
+
const iframeType = this.mapIframeNameToType(iframe);
|
|
2099
|
+
if (iframeType) {
|
|
2100
|
+
this.messageBroker.setIframeReady(iframeType);
|
|
2101
|
+
}
|
|
2102
|
+
});
|
|
2103
|
+
// Listen for launcher click events from the iframe
|
|
2104
|
+
window.addEventListener('message', (event) => {
|
|
2105
|
+
// Log all messages from iframes for debugging
|
|
2106
|
+
if (event.data?.type) {
|
|
2107
|
+
console.log('[Weld SDK] Received message:', event.data.type);
|
|
2108
|
+
}
|
|
2109
|
+
if (event.data?.type === 'launcher:clicked') {
|
|
2110
|
+
// Toggle behavior - if widget is open, close it; if closed, open it
|
|
2111
|
+
const state = this.stateCoordinator.getState();
|
|
2112
|
+
if (state.widget.isOpen) {
|
|
2113
|
+
console.log('[Weld SDK] Launcher clicked - closing widget (toggle)');
|
|
2114
|
+
this.close();
|
|
2115
|
+
}
|
|
2116
|
+
else {
|
|
2117
|
+
console.log('[Weld SDK] Launcher clicked - opening widget');
|
|
2118
|
+
this.open();
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
if (event.data?.type === 'weld:close') {
|
|
2122
|
+
console.log('[Weld SDK] Widget close requested');
|
|
2123
|
+
this.close();
|
|
2124
|
+
}
|
|
2125
|
+
});
|
|
2126
|
+
}
|
|
2127
|
+
/**
|
|
2128
|
+
* Map iframe name to type
|
|
2129
|
+
*/
|
|
2130
|
+
mapIframeNameToType(name) {
|
|
2131
|
+
if (name.includes('launcher'))
|
|
2132
|
+
return IframeType.LAUNCHER;
|
|
2133
|
+
if (name.includes('widget'))
|
|
2134
|
+
return IframeType.WIDGET;
|
|
2135
|
+
return null;
|
|
2136
|
+
}
|
|
2137
|
+
/**
|
|
2138
|
+
* Wait for all iframes to be ready
|
|
2139
|
+
*/
|
|
2140
|
+
async waitForIframesReady(timeout = 10000) {
|
|
2141
|
+
const startTime = Date.now();
|
|
2142
|
+
return new Promise((resolve, reject) => {
|
|
2143
|
+
const checkReady = () => {
|
|
2144
|
+
if (this.iframeManager.areAllIframesReady()) {
|
|
2145
|
+
resolve();
|
|
2146
|
+
return;
|
|
2147
|
+
}
|
|
2148
|
+
if (Date.now() - startTime > timeout) {
|
|
2149
|
+
reject(new Error('Timeout waiting for iframes to be ready'));
|
|
2150
|
+
return;
|
|
2151
|
+
}
|
|
2152
|
+
setTimeout(checkReady, 100);
|
|
2153
|
+
};
|
|
2154
|
+
checkReady();
|
|
2155
|
+
});
|
|
2156
|
+
}
|
|
2157
|
+
/**
|
|
2158
|
+
* Wait for SDK to be ready
|
|
2159
|
+
*/
|
|
2160
|
+
async ready() {
|
|
2161
|
+
return this.readyPromise;
|
|
2162
|
+
}
|
|
2163
|
+
/**
|
|
2164
|
+
* Check if SDK is ready
|
|
2165
|
+
*/
|
|
2166
|
+
isReady() {
|
|
2167
|
+
return this.status === SDKStatus.READY;
|
|
2168
|
+
}
|
|
2169
|
+
/**
|
|
2170
|
+
* Open the widget
|
|
2171
|
+
*/
|
|
2172
|
+
open() {
|
|
2173
|
+
this.ensureReady();
|
|
2174
|
+
console.log('[Weld SDK] Opening widget...');
|
|
2175
|
+
this.stateCoordinator.openWidget();
|
|
2176
|
+
this.iframeManager.showIframe(IframeType.WIDGET);
|
|
2177
|
+
this.iframeManager.showIframe(IframeType.BACKDROP);
|
|
2178
|
+
// Keep launcher visible so user can click it to close the widget
|
|
2179
|
+
// Send open message to the widget iframe
|
|
2180
|
+
const widgetIframe = this.iframeManager.getIframe(IframeType.WIDGET);
|
|
2181
|
+
if (widgetIframe?.element?.contentWindow) {
|
|
2182
|
+
widgetIframe.element.contentWindow.postMessage({ type: 'weld:open' }, '*');
|
|
2183
|
+
}
|
|
2184
|
+
// Notify launcher that widget is now open (so it can show X icon)
|
|
2185
|
+
const launcherIframe = this.iframeManager.getIframe(IframeType.LAUNCHER);
|
|
2186
|
+
if (launcherIframe?.element?.contentWindow) {
|
|
2187
|
+
launcherIframe.element.contentWindow.postMessage({ type: 'weld:widget-opened' }, '*');
|
|
2188
|
+
}
|
|
2189
|
+
this.config.onOpen?.();
|
|
2190
|
+
}
|
|
2191
|
+
/**
|
|
2192
|
+
* Close the widget
|
|
2193
|
+
*/
|
|
2194
|
+
close() {
|
|
2195
|
+
this.ensureReady();
|
|
2196
|
+
console.log('[Weld SDK] Closing widget...');
|
|
2197
|
+
this.stateCoordinator.closeWidget();
|
|
2198
|
+
this.iframeManager.hideIframe(IframeType.WIDGET);
|
|
2199
|
+
this.iframeManager.hideIframe(IframeType.BACKDROP);
|
|
2200
|
+
// Launcher stays visible
|
|
2201
|
+
// Send close message to the widget iframe
|
|
2202
|
+
const widgetIframe = this.iframeManager.getIframe(IframeType.WIDGET);
|
|
2203
|
+
if (widgetIframe?.element?.contentWindow) {
|
|
2204
|
+
widgetIframe.element.contentWindow.postMessage({ type: 'weld:close' }, '*');
|
|
2205
|
+
}
|
|
2206
|
+
// Notify launcher that widget is now closed (so it can show chat icon)
|
|
2207
|
+
const launcherIframe = this.iframeManager.getIframe(IframeType.LAUNCHER);
|
|
2208
|
+
if (launcherIframe?.element?.contentWindow) {
|
|
2209
|
+
launcherIframe.element.contentWindow.postMessage({ type: 'weld:widget-closed' }, '*');
|
|
2210
|
+
}
|
|
2211
|
+
this.config.onClose?.();
|
|
2212
|
+
}
|
|
2213
|
+
/**
|
|
2214
|
+
* Toggle widget open/close
|
|
2215
|
+
*/
|
|
2216
|
+
toggle() {
|
|
2217
|
+
const state = this.stateCoordinator.getState();
|
|
2218
|
+
if (state.widget.isOpen) {
|
|
2219
|
+
this.close();
|
|
2220
|
+
}
|
|
2221
|
+
else {
|
|
2222
|
+
this.open();
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
/**
|
|
2226
|
+
* Minimize the widget
|
|
2227
|
+
*/
|
|
2228
|
+
minimize() {
|
|
2229
|
+
this.ensureReady();
|
|
2230
|
+
this.logger.debug('Minimizing widget');
|
|
2231
|
+
this.stateCoordinator.minimizeWidget();
|
|
2232
|
+
this.config.onMinimize?.();
|
|
2233
|
+
}
|
|
2234
|
+
/**
|
|
2235
|
+
* Maximize the widget
|
|
2236
|
+
*/
|
|
2237
|
+
maximize() {
|
|
2238
|
+
this.ensureReady();
|
|
2239
|
+
this.logger.debug('Maximizing widget');
|
|
2240
|
+
this.stateCoordinator.maximizeWidget();
|
|
2241
|
+
this.config.onMaximize?.();
|
|
2242
|
+
}
|
|
2243
|
+
/**
|
|
2244
|
+
* Show the launcher
|
|
2245
|
+
*/
|
|
2246
|
+
showLauncher() {
|
|
2247
|
+
this.ensureReady();
|
|
2248
|
+
this.logger.debug('Showing launcher');
|
|
2249
|
+
this.iframeManager.showIframe(IframeType.LAUNCHER);
|
|
2250
|
+
this.stateCoordinator.updateState('launcher.isVisible', true);
|
|
2251
|
+
}
|
|
2252
|
+
/**
|
|
2253
|
+
* Hide the launcher
|
|
2254
|
+
*/
|
|
2255
|
+
hideLauncher() {
|
|
2256
|
+
this.ensureReady();
|
|
2257
|
+
this.logger.debug('Hiding launcher');
|
|
2258
|
+
this.iframeManager.hideIframe(IframeType.LAUNCHER);
|
|
2259
|
+
this.stateCoordinator.updateState('launcher.isVisible', false);
|
|
2260
|
+
}
|
|
2261
|
+
/**
|
|
2262
|
+
* Set badge count
|
|
2263
|
+
*/
|
|
2264
|
+
setBadgeCount(count) {
|
|
2265
|
+
this.ensureReady();
|
|
2266
|
+
this.logger.debug('Setting badge count', { count });
|
|
2267
|
+
this.stateCoordinator.setBadgeCount(count);
|
|
2268
|
+
}
|
|
2269
|
+
/**
|
|
2270
|
+
* Clear badge
|
|
2271
|
+
*/
|
|
2272
|
+
clearBadge() {
|
|
2273
|
+
this.setBadgeCount(0);
|
|
2274
|
+
}
|
|
2275
|
+
/**
|
|
2276
|
+
* Send a message
|
|
2277
|
+
*/
|
|
2278
|
+
sendMessage(text, metadata) {
|
|
2279
|
+
this.ensureReady();
|
|
2280
|
+
this.logger.debug('Sending message', { text });
|
|
2281
|
+
this.messageBroker.sendToIframe(IframeType.WIDGET, 'weld:message:send', {
|
|
2282
|
+
text,
|
|
2283
|
+
metadata,
|
|
2284
|
+
timestamp: Date.now(),
|
|
2285
|
+
});
|
|
2286
|
+
}
|
|
2287
|
+
/**
|
|
2288
|
+
* Identify user
|
|
2289
|
+
*/
|
|
2290
|
+
identify(identity) {
|
|
2291
|
+
this.ensureReady();
|
|
2292
|
+
this.logger.debug('Identifying user', { userId: identity.userId });
|
|
2293
|
+
this.stateCoordinator.setUserAuth(identity.userId, identity.email, identity.name);
|
|
2294
|
+
this.messageBroker.broadcast('weld:auth:login', identity);
|
|
2295
|
+
}
|
|
2296
|
+
/**
|
|
2297
|
+
* Logout user
|
|
2298
|
+
*/
|
|
2299
|
+
logout() {
|
|
2300
|
+
this.ensureReady();
|
|
2301
|
+
this.logger.debug('Logging out user');
|
|
2302
|
+
this.stateCoordinator.updateState('user', {
|
|
2303
|
+
isAuthenticated: false,
|
|
2304
|
+
isAnonymous: true,
|
|
2305
|
+
});
|
|
2306
|
+
this.messageBroker.broadcast('weld:auth:logout', {});
|
|
2307
|
+
}
|
|
2308
|
+
/**
|
|
2309
|
+
* Update configuration
|
|
2310
|
+
*/
|
|
2311
|
+
updateConfig(updates) {
|
|
2312
|
+
this.ensureReady();
|
|
2313
|
+
this.logger.debug('Updating configuration');
|
|
2314
|
+
// Merge config
|
|
2315
|
+
this.config = resolveConfig({ ...this.config, ...updates });
|
|
2316
|
+
// Broadcast config update
|
|
2317
|
+
this.messageBroker.broadcast('weld:config:update', updates);
|
|
2318
|
+
}
|
|
2319
|
+
/**
|
|
2320
|
+
* Update theme
|
|
2321
|
+
*/
|
|
2322
|
+
setTheme(theme) {
|
|
2323
|
+
this.ensureReady();
|
|
2324
|
+
this.logger.debug('Setting theme', { theme });
|
|
2325
|
+
this.stateCoordinator.updateState('ui.theme', theme);
|
|
2326
|
+
this.messageBroker.broadcast('weld:theme:update', { mode: theme });
|
|
2327
|
+
}
|
|
2328
|
+
/**
|
|
2329
|
+
* Update locale
|
|
2330
|
+
*/
|
|
2331
|
+
setLocale(locale) {
|
|
2332
|
+
this.ensureReady();
|
|
2333
|
+
this.logger.debug('Setting locale', { locale });
|
|
2334
|
+
this.stateCoordinator.updateState('ui.locale', locale);
|
|
2335
|
+
this.messageBroker.broadcast('weld:locale:update', { locale });
|
|
2336
|
+
}
|
|
2337
|
+
/**
|
|
2338
|
+
* Track custom event
|
|
2339
|
+
*/
|
|
2340
|
+
track(eventName, properties) {
|
|
2341
|
+
this.ensureReady();
|
|
2342
|
+
this.logger.debug('Tracking event', { eventName });
|
|
2343
|
+
this.messageBroker.broadcast('weld:event:track', {
|
|
2344
|
+
name: eventName,
|
|
2345
|
+
properties,
|
|
2346
|
+
timestamp: Date.now(),
|
|
2347
|
+
});
|
|
2348
|
+
}
|
|
2349
|
+
/**
|
|
2350
|
+
* Get current state
|
|
2351
|
+
*/
|
|
2352
|
+
getState() {
|
|
2353
|
+
return this.stateCoordinator.getState();
|
|
2354
|
+
}
|
|
2355
|
+
/**
|
|
2356
|
+
* Subscribe to state changes
|
|
2357
|
+
*/
|
|
2358
|
+
onStateChange(path, listener) {
|
|
2359
|
+
this.ensureReady();
|
|
2360
|
+
const subscriptionId = this.stateCoordinator.subscribe(path, listener);
|
|
2361
|
+
// Return unsubscribe function
|
|
2362
|
+
return () => {
|
|
2363
|
+
this.stateCoordinator.unsubscribe(subscriptionId);
|
|
2364
|
+
};
|
|
2365
|
+
}
|
|
2366
|
+
/**
|
|
2367
|
+
* Get device info
|
|
2368
|
+
*/
|
|
2369
|
+
getDeviceInfo() {
|
|
2370
|
+
return this.iframeManager.getDeviceInfo();
|
|
2371
|
+
}
|
|
2372
|
+
/**
|
|
2373
|
+
* Get SDK status
|
|
2374
|
+
*/
|
|
2375
|
+
getStatus() {
|
|
2376
|
+
return this.status;
|
|
2377
|
+
}
|
|
2378
|
+
/**
|
|
2379
|
+
* Get SDK version
|
|
2380
|
+
*/
|
|
2381
|
+
getVersion() {
|
|
2382
|
+
return packageJson.version;
|
|
2383
|
+
}
|
|
2384
|
+
/**
|
|
2385
|
+
* Enable debug mode
|
|
2386
|
+
*/
|
|
2387
|
+
enableDebug() {
|
|
2388
|
+
this.logger.setLevel('debug');
|
|
2389
|
+
this.logger.info('Debug mode enabled');
|
|
2390
|
+
}
|
|
2391
|
+
/**
|
|
2392
|
+
* Disable debug mode
|
|
2393
|
+
*/
|
|
2394
|
+
disableDebug() {
|
|
2395
|
+
this.logger.setLevel('warn');
|
|
2396
|
+
this.logger.info('Debug mode disabled');
|
|
2397
|
+
}
|
|
2398
|
+
/**
|
|
2399
|
+
* Ensure SDK is ready before operation
|
|
2400
|
+
*/
|
|
2401
|
+
ensureReady() {
|
|
2402
|
+
if (this.status !== SDKStatus.READY) {
|
|
2403
|
+
throw new Error('SDK not ready. Call init() first.');
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
/**
|
|
2407
|
+
* Destroy SDK and cleanup
|
|
2408
|
+
*/
|
|
2409
|
+
destroy() {
|
|
2410
|
+
this.logger.info('Destroying WeldSDK');
|
|
2411
|
+
// Destroy modules
|
|
2412
|
+
this.stateCoordinator.destroy();
|
|
2413
|
+
this.messageBroker.destroy();
|
|
2414
|
+
this.iframeManager.destroy();
|
|
2415
|
+
// Reset status
|
|
2416
|
+
this.status = SDKStatus.DESTROYED;
|
|
2417
|
+
// Call onDestroy callback
|
|
2418
|
+
this.config.onDestroy?.();
|
|
2419
|
+
this.logger.info('WeldSDK destroyed');
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
/**
|
|
2424
|
+
* Angular component for the Helpdesk Widget
|
|
2425
|
+
*
|
|
2426
|
+
* This component provides a declarative way to add the widget to your app.
|
|
2427
|
+
* The widget loads in an iframe and doesn't render any visible children.
|
|
2428
|
+
*
|
|
2429
|
+
* @example
|
|
2430
|
+
* ```typescript
|
|
2431
|
+
* // app.component.ts
|
|
2432
|
+
* import { Component } from '@angular/core';
|
|
2433
|
+
*
|
|
2434
|
+
* @Component({
|
|
2435
|
+
* selector: 'app-root',
|
|
2436
|
+
* template: `
|
|
2437
|
+
* <h1>My App</h1>
|
|
2438
|
+
* <helpdesk-widget [widgetId]="'your-widget-id'"></helpdesk-widget>
|
|
2439
|
+
* `
|
|
2440
|
+
* })
|
|
2441
|
+
* export class AppComponent {}
|
|
2442
|
+
* ```
|
|
2443
|
+
*
|
|
2444
|
+
* @example With event handlers
|
|
2445
|
+
* ```typescript
|
|
2446
|
+
* @Component({
|
|
2447
|
+
* selector: 'app-root',
|
|
2448
|
+
* template: `
|
|
2449
|
+
* <helpdesk-widget
|
|
2450
|
+
* [widgetId]="'your-widget-id'"
|
|
2451
|
+
* [onReady]="handleReady"
|
|
2452
|
+
* [onOpen]="handleOpened">
|
|
2453
|
+
* </helpdesk-widget>
|
|
2454
|
+
* `
|
|
2455
|
+
* })
|
|
2456
|
+
* export class AppComponent {
|
|
2457
|
+
* handleReady = () => {
|
|
2458
|
+
* console.log('Widget ready');
|
|
2459
|
+
* }
|
|
2460
|
+
*
|
|
2461
|
+
* handleOpened = () => {
|
|
2462
|
+
* console.log('Widget opened');
|
|
2463
|
+
* }
|
|
2464
|
+
* }
|
|
2465
|
+
* ```
|
|
2466
|
+
*/
|
|
2467
|
+
let HelpdeskWidgetComponent = (() => {
|
|
2468
|
+
let _classDecorators = [core.Component({
|
|
2469
|
+
selector: 'helpdesk-widget',
|
|
2470
|
+
template: '',
|
|
2471
|
+
standalone: true,
|
|
2472
|
+
})];
|
|
2473
|
+
let _classDescriptor;
|
|
2474
|
+
let _classExtraInitializers = [];
|
|
2475
|
+
let _classThis;
|
|
2476
|
+
let _widgetId_decorators;
|
|
2477
|
+
let _widgetId_initializers = [];
|
|
2478
|
+
let _widgetId_extraInitializers = [];
|
|
2479
|
+
let _onReady_decorators;
|
|
2480
|
+
let _onReady_initializers = [];
|
|
2481
|
+
let _onReady_extraInitializers = [];
|
|
2482
|
+
let _onOpen_decorators;
|
|
2483
|
+
let _onOpen_initializers = [];
|
|
2484
|
+
let _onOpen_extraInitializers = [];
|
|
2485
|
+
let _onClose_decorators;
|
|
2486
|
+
let _onClose_initializers = [];
|
|
2487
|
+
let _onClose_extraInitializers = [];
|
|
2488
|
+
let _onError_decorators;
|
|
2489
|
+
let _onError_initializers = [];
|
|
2490
|
+
let _onError_extraInitializers = [];
|
|
2491
|
+
_classThis = class {
|
|
2492
|
+
constructor() {
|
|
2493
|
+
this.widgetId = __runInitializers(this, _widgetId_initializers, void 0);
|
|
2494
|
+
this.onReady = (__runInitializers(this, _widgetId_extraInitializers), __runInitializers(this, _onReady_initializers, void 0));
|
|
2495
|
+
this.onOpen = (__runInitializers(this, _onReady_extraInitializers), __runInitializers(this, _onOpen_initializers, void 0));
|
|
2496
|
+
this.onClose = (__runInitializers(this, _onOpen_extraInitializers), __runInitializers(this, _onClose_initializers, void 0));
|
|
2497
|
+
this.onError = (__runInitializers(this, _onClose_extraInitializers), __runInitializers(this, _onError_initializers, void 0));
|
|
2498
|
+
this.widget = (__runInitializers(this, _onError_extraInitializers), null);
|
|
2499
|
+
}
|
|
2500
|
+
ngOnInit() {
|
|
2501
|
+
if (!this.widgetId) {
|
|
2502
|
+
throw new Error('widgetId is required for HelpdeskWidgetComponent');
|
|
2503
|
+
}
|
|
2504
|
+
const config = {
|
|
2505
|
+
widgetId: this.widgetId,
|
|
2506
|
+
onReady: this.onReady,
|
|
2507
|
+
onOpen: this.onOpen,
|
|
2508
|
+
onClose: this.onClose,
|
|
2509
|
+
onError: this.onError,
|
|
2510
|
+
};
|
|
2511
|
+
try {
|
|
2512
|
+
this.widget = new WeldSDK(config);
|
|
2513
|
+
this.widget.init();
|
|
2514
|
+
}
|
|
2515
|
+
catch (error) {
|
|
2516
|
+
console.error('Failed to initialize Helpdesk Widget:', error);
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
ngOnDestroy() {
|
|
2520
|
+
if (this.widget) {
|
|
2521
|
+
this.widget.destroy();
|
|
2522
|
+
this.widget = null;
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
/**
|
|
2526
|
+
* Open the widget programmatically
|
|
2527
|
+
*/
|
|
2528
|
+
open() {
|
|
2529
|
+
this.widget?.open();
|
|
2530
|
+
}
|
|
2531
|
+
/**
|
|
2532
|
+
* Close the widget programmatically
|
|
2533
|
+
*/
|
|
2534
|
+
close() {
|
|
2535
|
+
this.widget?.close();
|
|
2536
|
+
}
|
|
2537
|
+
/**
|
|
2538
|
+
* Toggle widget visibility programmatically
|
|
2539
|
+
*/
|
|
2540
|
+
toggle() {
|
|
2541
|
+
this.widget?.toggle();
|
|
2542
|
+
}
|
|
2543
|
+
/**
|
|
2544
|
+
* Send a message to the widget
|
|
2545
|
+
*/
|
|
2546
|
+
sendMessage(message) {
|
|
2547
|
+
this.widget?.sendMessage(message);
|
|
2548
|
+
}
|
|
2549
|
+
};
|
|
2550
|
+
__setFunctionName(_classThis, "HelpdeskWidgetComponent");
|
|
2551
|
+
(() => {
|
|
2552
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
2553
|
+
_widgetId_decorators = [core.Input()];
|
|
2554
|
+
_onReady_decorators = [core.Input()];
|
|
2555
|
+
_onOpen_decorators = [core.Input()];
|
|
2556
|
+
_onClose_decorators = [core.Input()];
|
|
2557
|
+
_onError_decorators = [core.Input()];
|
|
2558
|
+
__esDecorate(null, null, _widgetId_decorators, { kind: "field", name: "widgetId", static: false, private: false, access: { has: obj => "widgetId" in obj, get: obj => obj.widgetId, set: (obj, value) => { obj.widgetId = value; } }, metadata: _metadata }, _widgetId_initializers, _widgetId_extraInitializers);
|
|
2559
|
+
__esDecorate(null, null, _onReady_decorators, { kind: "field", name: "onReady", static: false, private: false, access: { has: obj => "onReady" in obj, get: obj => obj.onReady, set: (obj, value) => { obj.onReady = value; } }, metadata: _metadata }, _onReady_initializers, _onReady_extraInitializers);
|
|
2560
|
+
__esDecorate(null, null, _onOpen_decorators, { kind: "field", name: "onOpen", static: false, private: false, access: { has: obj => "onOpen" in obj, get: obj => obj.onOpen, set: (obj, value) => { obj.onOpen = value; } }, metadata: _metadata }, _onOpen_initializers, _onOpen_extraInitializers);
|
|
2561
|
+
__esDecorate(null, null, _onClose_decorators, { kind: "field", name: "onClose", static: false, private: false, access: { has: obj => "onClose" in obj, get: obj => obj.onClose, set: (obj, value) => { obj.onClose = value; } }, metadata: _metadata }, _onClose_initializers, _onClose_extraInitializers);
|
|
2562
|
+
__esDecorate(null, null, _onError_decorators, { kind: "field", name: "onError", static: false, private: false, access: { has: obj => "onError" in obj, get: obj => obj.onError, set: (obj, value) => { obj.onError = value; } }, metadata: _metadata }, _onError_initializers, _onError_extraInitializers);
|
|
2563
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
2564
|
+
_classThis = _classDescriptor.value;
|
|
2565
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
2566
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
2567
|
+
})();
|
|
2568
|
+
return _classThis;
|
|
2569
|
+
})();
|
|
2570
|
+
|
|
2571
|
+
/**
|
|
2572
|
+
* Angular service for managing the Helpdesk Widget
|
|
2573
|
+
*
|
|
2574
|
+
* @example
|
|
2575
|
+
* ```typescript
|
|
2576
|
+
* import { Component } from '@angular/core';
|
|
2577
|
+
* import { HelpdeskWidgetService } from '@weldsuite/helpdesk-widget-sdk/angular';
|
|
2578
|
+
*
|
|
2579
|
+
* @Component({
|
|
2580
|
+
* selector: 'app-root',
|
|
2581
|
+
* template: `
|
|
2582
|
+
* <button (click)="openWidget()">Show Support</button>
|
|
2583
|
+
* `
|
|
2584
|
+
* })
|
|
2585
|
+
* export class AppComponent {
|
|
2586
|
+
* constructor(private helpdeskService: HelpdeskWidgetService) {
|
|
2587
|
+
* this.helpdeskService.initialize({ widgetId: 'your-widget-id' });
|
|
2588
|
+
* }
|
|
2589
|
+
*
|
|
2590
|
+
* openWidget() {
|
|
2591
|
+
* this.helpdeskService.open();
|
|
2592
|
+
* }
|
|
2593
|
+
* }
|
|
2594
|
+
* ```
|
|
2595
|
+
*/
|
|
2596
|
+
let HelpdeskWidgetService = (() => {
|
|
2597
|
+
let _classDecorators = [core.Injectable({
|
|
2598
|
+
providedIn: 'root',
|
|
2599
|
+
})];
|
|
2600
|
+
let _classDescriptor;
|
|
2601
|
+
let _classExtraInitializers = [];
|
|
2602
|
+
let _classThis;
|
|
2603
|
+
_classThis = class {
|
|
2604
|
+
constructor() {
|
|
2605
|
+
this.widget = null;
|
|
2606
|
+
}
|
|
2607
|
+
/**
|
|
2608
|
+
* Initialize the widget with configuration
|
|
2609
|
+
*/
|
|
2610
|
+
initialize(config) {
|
|
2611
|
+
if (this.widget) {
|
|
2612
|
+
console.warn('Helpdesk widget is already initialized. Destroying previous instance.');
|
|
2613
|
+
this.destroy();
|
|
2614
|
+
}
|
|
2615
|
+
try {
|
|
2616
|
+
this.widget = new WeldSDK(config);
|
|
2617
|
+
this.widget.init();
|
|
2618
|
+
}
|
|
2619
|
+
catch (error) {
|
|
2620
|
+
console.error('Failed to initialize Helpdesk Widget:', error);
|
|
2621
|
+
throw error;
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
/**
|
|
2625
|
+
* Open the widget
|
|
2626
|
+
*/
|
|
2627
|
+
open() {
|
|
2628
|
+
if (!this.widget) {
|
|
2629
|
+
console.warn('Widget not initialized. Call initialize() first.');
|
|
2630
|
+
return;
|
|
2631
|
+
}
|
|
2632
|
+
this.widget.open();
|
|
2633
|
+
}
|
|
2634
|
+
/**
|
|
2635
|
+
* Close the widget
|
|
2636
|
+
*/
|
|
2637
|
+
close() {
|
|
2638
|
+
if (!this.widget) {
|
|
2639
|
+
console.warn('Widget not initialized. Call initialize() first.');
|
|
2640
|
+
return;
|
|
2641
|
+
}
|
|
2642
|
+
this.widget.close();
|
|
2643
|
+
}
|
|
2644
|
+
/**
|
|
2645
|
+
* Toggle widget visibility
|
|
2646
|
+
*/
|
|
2647
|
+
toggle() {
|
|
2648
|
+
if (!this.widget) {
|
|
2649
|
+
console.warn('Widget not initialized. Call initialize() first.');
|
|
2650
|
+
return;
|
|
2651
|
+
}
|
|
2652
|
+
this.widget.toggle();
|
|
2653
|
+
}
|
|
2654
|
+
/**
|
|
2655
|
+
* Send a message to the widget
|
|
2656
|
+
*/
|
|
2657
|
+
sendMessage(message) {
|
|
2658
|
+
if (!this.widget) {
|
|
2659
|
+
console.warn('Widget not initialized. Call initialize() first.');
|
|
2660
|
+
return;
|
|
2661
|
+
}
|
|
2662
|
+
this.widget.sendMessage(message);
|
|
2663
|
+
}
|
|
2664
|
+
/**
|
|
2665
|
+
* Subscribe to state changes
|
|
2666
|
+
* @returns Unsubscribe function
|
|
2667
|
+
*/
|
|
2668
|
+
onStateChange(path, listener) {
|
|
2669
|
+
if (!this.widget) {
|
|
2670
|
+
console.warn('Widget not initialized. Call initialize() first.');
|
|
2671
|
+
return () => { };
|
|
2672
|
+
}
|
|
2673
|
+
return this.widget.onStateChange(path, listener);
|
|
2674
|
+
}
|
|
2675
|
+
/**
|
|
2676
|
+
* Destroy the widget and clean up resources
|
|
2677
|
+
*/
|
|
2678
|
+
destroy() {
|
|
2679
|
+
if (this.widget) {
|
|
2680
|
+
this.widget.destroy();
|
|
2681
|
+
this.widget = null;
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
/**
|
|
2685
|
+
* Get the widget instance
|
|
2686
|
+
*/
|
|
2687
|
+
getWidget() {
|
|
2688
|
+
return this.widget;
|
|
2689
|
+
}
|
|
2690
|
+
/**
|
|
2691
|
+
* Check if SDK is ready
|
|
2692
|
+
*/
|
|
2693
|
+
isReady() {
|
|
2694
|
+
return this.widget?.isReady() ?? false;
|
|
2695
|
+
}
|
|
2696
|
+
ngOnDestroy() {
|
|
2697
|
+
this.destroy();
|
|
2698
|
+
}
|
|
2699
|
+
};
|
|
2700
|
+
__setFunctionName(_classThis, "HelpdeskWidgetService");
|
|
2701
|
+
(() => {
|
|
2702
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
2703
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
2704
|
+
_classThis = _classDescriptor.value;
|
|
2705
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
2706
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
2707
|
+
})();
|
|
2708
|
+
return _classThis;
|
|
2709
|
+
})();
|
|
2710
|
+
|
|
2711
|
+
/**
|
|
2712
|
+
* Angular module for the Helpdesk Widget
|
|
2713
|
+
*
|
|
2714
|
+
* This module is for backwards compatibility with Angular apps not using standalone components.
|
|
2715
|
+
* For modern Angular apps (v14+), prefer importing the standalone component directly.
|
|
2716
|
+
*
|
|
2717
|
+
* @example
|
|
2718
|
+
* ```typescript
|
|
2719
|
+
* // app.module.ts
|
|
2720
|
+
* import { HelpdeskWidgetModule } from '@weldsuite/helpdesk-widget-sdk/angular';
|
|
2721
|
+
*
|
|
2722
|
+
* @NgModule({
|
|
2723
|
+
* declarations: [AppComponent],
|
|
2724
|
+
* imports: [
|
|
2725
|
+
* BrowserModule,
|
|
2726
|
+
* HelpdeskWidgetModule
|
|
2727
|
+
* ],
|
|
2728
|
+
* providers: [],
|
|
2729
|
+
* bootstrap: [AppComponent]
|
|
2730
|
+
* })
|
|
2731
|
+
* export class AppModule { }
|
|
2732
|
+
* ```
|
|
2733
|
+
*
|
|
2734
|
+
* @example For standalone components (Angular 14+), import directly:
|
|
2735
|
+
* ```typescript
|
|
2736
|
+
* import { HelpdeskWidgetComponent } from '@weldsuite/helpdesk-widget-sdk/angular';
|
|
2737
|
+
*
|
|
2738
|
+
* @Component({
|
|
2739
|
+
* selector: 'app-root',
|
|
2740
|
+
* standalone: true,
|
|
2741
|
+
* imports: [HelpdeskWidgetComponent],
|
|
2742
|
+
* template: `<helpdesk-widget [widgetId]="'your-widget-id'"></helpdesk-widget>`
|
|
2743
|
+
* })
|
|
2744
|
+
* export class AppComponent {}
|
|
2745
|
+
* ```
|
|
2746
|
+
*/
|
|
2747
|
+
let HelpdeskWidgetModule = (() => {
|
|
2748
|
+
let _classDecorators = [core.NgModule({
|
|
2749
|
+
imports: [HelpdeskWidgetComponent],
|
|
2750
|
+
exports: [HelpdeskWidgetComponent],
|
|
2751
|
+
providers: [HelpdeskWidgetService],
|
|
2752
|
+
})];
|
|
2753
|
+
let _classDescriptor;
|
|
2754
|
+
let _classExtraInitializers = [];
|
|
2755
|
+
let _classThis;
|
|
2756
|
+
_classThis = class {
|
|
2757
|
+
};
|
|
2758
|
+
__setFunctionName(_classThis, "HelpdeskWidgetModule");
|
|
2759
|
+
(() => {
|
|
2760
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
2761
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
2762
|
+
_classThis = _classDescriptor.value;
|
|
2763
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
2764
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
2765
|
+
})();
|
|
2766
|
+
return _classThis;
|
|
2767
|
+
})();
|
|
2768
|
+
|
|
2769
|
+
exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
|
|
2770
|
+
exports.HelpdeskWidget = WeldSDK;
|
|
2771
|
+
exports.HelpdeskWidgetComponent = HelpdeskWidgetComponent;
|
|
2772
|
+
exports.HelpdeskWidgetModule = HelpdeskWidgetModule;
|
|
2773
|
+
exports.HelpdeskWidgetService = HelpdeskWidgetService;
|
|
2774
|
+
exports.WeldSDK = WeldSDK;
|
|
2775
|
+
//# sourceMappingURL=angular.js.map
|