@corti/embedded-web 0.1.0-alpha.1
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 +273 -0
- package/dist/CortiEmbedded.d.ts +102 -0
- package/dist/CortiEmbedded.js +452 -0
- package/dist/CortiEmbedded.js.map +1 -0
- package/dist/bundle.js +96 -0
- package/dist/corti-embedded.d.ts +1 -0
- package/dist/corti-embedded.js +5 -0
- package/dist/corti-embedded.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/internal-types.d.ts +231 -0
- package/dist/internal-types.js +2 -0
- package/dist/internal-types.js.map +1 -0
- package/dist/public-types.d.ts +238 -0
- package/dist/public-types.js +2 -0
- package/dist/public-types.js.map +1 -0
- package/dist/react/CortiEmbeddedReact.d.ts +25 -0
- package/dist/react/CortiEmbeddedReact.js +28 -0
- package/dist/react/CortiEmbeddedReact.js.map +1 -0
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.js +2 -0
- package/dist/react/index.js.map +1 -0
- package/dist/styles/base.d.ts +4 -0
- package/dist/styles/base.js +10 -0
- package/dist/styles/base.js.map +1 -0
- package/dist/styles/container-styles.d.ts +1 -0
- package/dist/styles/container-styles.js +35 -0
- package/dist/styles/container-styles.js.map +1 -0
- package/dist/styles/theme.d.ts +2 -0
- package/dist/styles/theme.js +101 -0
- package/dist/styles/theme.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/utils/PostMessageHandler.d.ts +119 -0
- package/dist/utils/PostMessageHandler.js +353 -0
- package/dist/utils/PostMessageHandler.js.map +1 -0
- package/dist/utils/baseUrl.d.ts +1 -0
- package/dist/utils/baseUrl.js +25 -0
- package/dist/utils/baseUrl.js.map +1 -0
- package/dist/utils/embedUrl.d.ts +2 -0
- package/dist/utils/embedUrl.js +23 -0
- package/dist/utils/embedUrl.js.map +1 -0
- package/dist/utils/errorFormatter.d.ts +10 -0
- package/dist/utils/errorFormatter.js +163 -0
- package/dist/utils/errorFormatter.js.map +1 -0
- package/dist/web-bundle.js +89 -0
- package/dist/web-index.d.ts +3 -0
- package/dist/web-index.js +3 -0
- package/dist/web-index.js.map +1 -0
- package/package.json +129 -0
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
export class PostMessageHandler {
|
|
2
|
+
constructor(iframe, callbacks = {}) {
|
|
3
|
+
this.pendingRequests = new Map();
|
|
4
|
+
this.messageListener = null;
|
|
5
|
+
this.isReady = false;
|
|
6
|
+
this.iframe = iframe;
|
|
7
|
+
this.callbacks = callbacks;
|
|
8
|
+
this.setupMessageListener();
|
|
9
|
+
}
|
|
10
|
+
setupMessageListener() {
|
|
11
|
+
this.messageListener = (event) => {
|
|
12
|
+
// Only handle messages from our iframe
|
|
13
|
+
if (event.source !== this.iframe.contentWindow) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
// Enforce origin to match the trusted iframe origin
|
|
17
|
+
const trustedOrigin = this.getTrustedOrigin();
|
|
18
|
+
if (!trustedOrigin || event.origin !== trustedOrigin) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const { data } = event;
|
|
22
|
+
// Check for Corti embedded events
|
|
23
|
+
if (data?.type === 'CORTI_EMBEDDED_EVENT') {
|
|
24
|
+
this.handleEvent(data);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
// Check if this is a response to a pending request
|
|
28
|
+
if (data.requestId && this.pendingRequests.has(data.requestId)) {
|
|
29
|
+
this.handleResponse(data);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
window.addEventListener('message', this.messageListener);
|
|
33
|
+
}
|
|
34
|
+
handleEvent(eventData) {
|
|
35
|
+
const eventType = eventData.event;
|
|
36
|
+
const { payload } = eventData;
|
|
37
|
+
// Handle ready-like events
|
|
38
|
+
if (eventType === 'ready' || eventType === 'loaded') {
|
|
39
|
+
this.isReady = true;
|
|
40
|
+
}
|
|
41
|
+
// Handle specific events with callbacks
|
|
42
|
+
switch (eventType) {
|
|
43
|
+
case 'ready':
|
|
44
|
+
case 'loaded':
|
|
45
|
+
this.callbacks.onReady?.();
|
|
46
|
+
break;
|
|
47
|
+
case 'authChanged':
|
|
48
|
+
this.callbacks.onAuthChanged?.(payload);
|
|
49
|
+
break;
|
|
50
|
+
case 'interactionCreated':
|
|
51
|
+
this.callbacks.onInteractionCreated?.(payload);
|
|
52
|
+
break;
|
|
53
|
+
case 'recordingStarted':
|
|
54
|
+
this.callbacks.onRecordingStarted?.();
|
|
55
|
+
break;
|
|
56
|
+
case 'recordingStopped':
|
|
57
|
+
this.callbacks.onRecordingStopped?.();
|
|
58
|
+
break;
|
|
59
|
+
case 'documentGenerated':
|
|
60
|
+
this.callbacks.onDocumentGenerated?.(payload);
|
|
61
|
+
break;
|
|
62
|
+
case 'documentUpdated':
|
|
63
|
+
this.callbacks.onDocumentUpdated?.(payload);
|
|
64
|
+
break;
|
|
65
|
+
case 'navigationChanged':
|
|
66
|
+
this.callbacks.onNavigationChanged?.(payload);
|
|
67
|
+
break;
|
|
68
|
+
case 'usage':
|
|
69
|
+
this.callbacks.onUsage?.(payload);
|
|
70
|
+
break;
|
|
71
|
+
default:
|
|
72
|
+
console.warn(`Unhandled event type: ${eventType}`);
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
handleResponse(data) {
|
|
77
|
+
const pendingRequest = this.pendingRequests.get(data.requestId);
|
|
78
|
+
if (pendingRequest) {
|
|
79
|
+
const { resolve, reject } = pendingRequest;
|
|
80
|
+
this.pendingRequests.delete(data.requestId);
|
|
81
|
+
if (data.success === false || data.error) {
|
|
82
|
+
const error = {
|
|
83
|
+
message: data.error || 'Request failed',
|
|
84
|
+
code: data.errorCode,
|
|
85
|
+
details: data.errorDetails,
|
|
86
|
+
};
|
|
87
|
+
this.callbacks.onError?.(error);
|
|
88
|
+
reject(error);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
resolve(data);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
destroy() {
|
|
96
|
+
if (this.messageListener) {
|
|
97
|
+
window.removeEventListener('message', this.messageListener);
|
|
98
|
+
this.messageListener = null;
|
|
99
|
+
}
|
|
100
|
+
this.pendingRequests.clear();
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Update callbacks after construction
|
|
104
|
+
*/
|
|
105
|
+
updateCallbacks(callbacks) {
|
|
106
|
+
this.callbacks = { ...this.callbacks, ...callbacks };
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Check if the iframe is ready to receive postMessages
|
|
110
|
+
*/
|
|
111
|
+
get ready() {
|
|
112
|
+
return this.isReady;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Wait for the iframe to be ready
|
|
116
|
+
* @param timeout - Optional timeout in milliseconds (default: 30000ms)
|
|
117
|
+
* @returns Promise that resolves when ready
|
|
118
|
+
*/
|
|
119
|
+
async waitForReady(timeout = 30000) {
|
|
120
|
+
if (this.isReady) {
|
|
121
|
+
return Promise.resolve();
|
|
122
|
+
}
|
|
123
|
+
return new Promise((resolve, reject) => {
|
|
124
|
+
const timeoutId = setTimeout(() => {
|
|
125
|
+
reject(new Error('Timeout waiting for iframe to be ready'));
|
|
126
|
+
}, timeout);
|
|
127
|
+
// Create a one-time listener for the ready event
|
|
128
|
+
const readyListener = (event) => {
|
|
129
|
+
if (event.source === this.iframe.contentWindow &&
|
|
130
|
+
event.origin === this.getTrustedOrigin() &&
|
|
131
|
+
event.data?.type === 'CORTI_EMBEDDED_EVENT' &&
|
|
132
|
+
event.data.event === 'ready') {
|
|
133
|
+
clearTimeout(timeoutId);
|
|
134
|
+
window.removeEventListener('message', readyListener);
|
|
135
|
+
resolve();
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
window.addEventListener('message', readyListener);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Sends a postMessage to the iframe and returns a Promise that resolves with the response
|
|
143
|
+
* @param message - The message to send
|
|
144
|
+
* @param timeout - Optional timeout in milliseconds (default: 10000ms)
|
|
145
|
+
* @returns Promise that resolves with the response
|
|
146
|
+
*/
|
|
147
|
+
async postMessage(message, timeout = 10000) {
|
|
148
|
+
if (!this.iframe.contentWindow) {
|
|
149
|
+
throw new Error('Iframe not ready');
|
|
150
|
+
}
|
|
151
|
+
// Ensure the iframe has signaled readiness before sending
|
|
152
|
+
await this.waitForReady();
|
|
153
|
+
const { contentWindow } = this.iframe;
|
|
154
|
+
const requestId = PostMessageHandler.generateRequestId();
|
|
155
|
+
return new Promise((resolve, reject) => {
|
|
156
|
+
// Set up timeout
|
|
157
|
+
const timeoutId = setTimeout(() => {
|
|
158
|
+
this.pendingRequests.delete(requestId);
|
|
159
|
+
reject(new Error('Request timeout'));
|
|
160
|
+
}, timeout);
|
|
161
|
+
// Store the promise handlers
|
|
162
|
+
const handlers = {
|
|
163
|
+
resolve: (value) => {
|
|
164
|
+
clearTimeout(timeoutId);
|
|
165
|
+
resolve(value);
|
|
166
|
+
},
|
|
167
|
+
reject: (reason) => {
|
|
168
|
+
clearTimeout(timeoutId);
|
|
169
|
+
reject(reason);
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
this.pendingRequests.set(requestId, handlers);
|
|
173
|
+
// Send the message
|
|
174
|
+
const fullMessage = {
|
|
175
|
+
...message,
|
|
176
|
+
requestId,
|
|
177
|
+
};
|
|
178
|
+
const targetOrigin = this.getTrustedOrigin();
|
|
179
|
+
if (!targetOrigin) {
|
|
180
|
+
this.pendingRequests.delete(requestId);
|
|
181
|
+
reject(new Error('Cannot determine trusted origin for postMessage'));
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
contentWindow.postMessage(fullMessage, targetOrigin);
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Helper method to send an auth message and return clean user data
|
|
189
|
+
* @param payload - Auth payload
|
|
190
|
+
* @returns Promise that resolves with user data
|
|
191
|
+
*/
|
|
192
|
+
async auth(payload) {
|
|
193
|
+
const response = await this.postMessage({
|
|
194
|
+
type: 'CORTI_EMBEDDED',
|
|
195
|
+
version: 'v1',
|
|
196
|
+
action: 'auth',
|
|
197
|
+
payload,
|
|
198
|
+
});
|
|
199
|
+
this.isReady = false;
|
|
200
|
+
if (response.payload && typeof response.success) {
|
|
201
|
+
return response.payload.user;
|
|
202
|
+
}
|
|
203
|
+
throw new Error(response.error);
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Helper method to configure a session
|
|
207
|
+
* @param payload - Session configuration payload
|
|
208
|
+
* @returns Promise that resolves when configuration is complete
|
|
209
|
+
*/
|
|
210
|
+
async configureSession(payload) {
|
|
211
|
+
await this.postMessage({
|
|
212
|
+
type: 'CORTI_EMBEDDED',
|
|
213
|
+
version: 'v1',
|
|
214
|
+
action: 'configureSession',
|
|
215
|
+
payload,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Helper method to navigate to a specific path
|
|
220
|
+
* @param payload - Navigation payload
|
|
221
|
+
* @returns Promise that resolves when navigation is complete
|
|
222
|
+
*/
|
|
223
|
+
async navigate(payload) {
|
|
224
|
+
await this.postMessage({
|
|
225
|
+
type: 'CORTI_EMBEDDED',
|
|
226
|
+
version: 'v1',
|
|
227
|
+
action: 'navigate',
|
|
228
|
+
payload,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Helper method to add facts to the session
|
|
233
|
+
* @param payload - Facts payload
|
|
234
|
+
* @returns Promise that resolves when facts are added
|
|
235
|
+
*/
|
|
236
|
+
async addFacts(payload) {
|
|
237
|
+
await this.postMessage({
|
|
238
|
+
type: 'CORTI_EMBEDDED',
|
|
239
|
+
version: 'v1',
|
|
240
|
+
action: 'addFacts',
|
|
241
|
+
payload,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Helper method to create a new interaction and return clean interaction data
|
|
246
|
+
* @param payload - Interaction creation payload
|
|
247
|
+
* @returns Promise that resolves with interaction details
|
|
248
|
+
*/
|
|
249
|
+
async createInteraction(payload) {
|
|
250
|
+
const response = await this.postMessage({
|
|
251
|
+
type: 'CORTI_EMBEDDED',
|
|
252
|
+
version: 'v1',
|
|
253
|
+
action: 'createInteraction',
|
|
254
|
+
payload,
|
|
255
|
+
});
|
|
256
|
+
if (response.payload && typeof response.success) {
|
|
257
|
+
return response.payload;
|
|
258
|
+
}
|
|
259
|
+
throw new Error(response.error);
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Helper method to start recording
|
|
263
|
+
* @returns Promise that resolves when recording starts
|
|
264
|
+
*/
|
|
265
|
+
async startRecording() {
|
|
266
|
+
await this.postMessage({
|
|
267
|
+
type: 'CORTI_EMBEDDED',
|
|
268
|
+
version: 'v1',
|
|
269
|
+
action: 'startRecording',
|
|
270
|
+
payload: {},
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Helper method to stop recording
|
|
275
|
+
* @returns Promise that resolves when recording stops
|
|
276
|
+
*/
|
|
277
|
+
async stopRecording() {
|
|
278
|
+
await this.postMessage({
|
|
279
|
+
type: 'CORTI_EMBEDDED',
|
|
280
|
+
version: 'v1',
|
|
281
|
+
action: 'stopRecording',
|
|
282
|
+
payload: {},
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Helper method to get current status
|
|
287
|
+
* @returns Promise that resolves with current status
|
|
288
|
+
*/
|
|
289
|
+
async getStatus() {
|
|
290
|
+
const response = await this.postMessage({
|
|
291
|
+
type: 'CORTI_EMBEDDED',
|
|
292
|
+
version: 'v1',
|
|
293
|
+
action: 'getStatus',
|
|
294
|
+
payload: {},
|
|
295
|
+
});
|
|
296
|
+
if (response.payload && typeof response.success) {
|
|
297
|
+
return response.payload;
|
|
298
|
+
}
|
|
299
|
+
throw new Error(response.error);
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Helper method to configure the component
|
|
303
|
+
* @param payload - Component configuration payload
|
|
304
|
+
* @returns Promise that resolves when configuration is applied
|
|
305
|
+
*/
|
|
306
|
+
async configure(payload) {
|
|
307
|
+
const response = await this.postMessage({
|
|
308
|
+
type: 'CORTI_EMBEDDED',
|
|
309
|
+
version: 'v1',
|
|
310
|
+
action: 'configure',
|
|
311
|
+
payload,
|
|
312
|
+
});
|
|
313
|
+
if (response.payload && typeof response.success) {
|
|
314
|
+
return response.payload;
|
|
315
|
+
}
|
|
316
|
+
throw new Error(response.error);
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Helper method to set credentials without triggering auth flow
|
|
320
|
+
* @param payload - Credentials payload
|
|
321
|
+
* @returns Promise that resolves when credentials are set
|
|
322
|
+
*/
|
|
323
|
+
async setCredentials(payload) {
|
|
324
|
+
await this.postMessage({
|
|
325
|
+
type: 'CORTI_EMBEDDED',
|
|
326
|
+
version: 'v1',
|
|
327
|
+
action: 'setCredentials',
|
|
328
|
+
payload,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
static generateRequestId() {
|
|
332
|
+
return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Derive the trusted origin from the iframe src (constructed from baseURL).
|
|
336
|
+
* Returns null if it cannot be determined.
|
|
337
|
+
*/
|
|
338
|
+
getTrustedOrigin() {
|
|
339
|
+
try {
|
|
340
|
+
// If iframe.src is relative, URL() will resolve against the current location.
|
|
341
|
+
// Embeds should provide an absolute baseURL; enforcing strict origin here.
|
|
342
|
+
const src = this.iframe.getAttribute('src') || this.iframe.src;
|
|
343
|
+
if (!src)
|
|
344
|
+
return null;
|
|
345
|
+
const url = new URL(src, window.location.href);
|
|
346
|
+
return url.origin;
|
|
347
|
+
}
|
|
348
|
+
catch {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
//# sourceMappingURL=PostMessageHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PostMessageHandler.js","sourceRoot":"","sources":["../../src/utils/PostMessageHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Corti } from '@corti/sdk';\nimport type {\n AddFactsPayload,\n AnyEmbeddedEvent,\n AuthPayload,\n AuthResponse,\n ConfigureAppPayload,\n ConfigureAppResponsePayload,\n ConfigureSessionPayload,\n CreateInteractionResponse,\n EmbeddedRequest,\n EmbeddedResponse,\n GetStatusResponse,\n NavigatePayload,\n SetCredentialsPayload,\n} from '../internal-types.js';\nimport type { EmbeddedEventData, InteractionPayload } from '../public-types.js';\n\nexport interface PostMessageHandlerCallbacks {\n onReady?: () => void;\n onAuthChanged?: (payload: any) => void;\n onInteractionCreated?: (payload: any) => void;\n onRecordingStarted?: () => void;\n onRecordingStopped?: () => void;\n onDocumentGenerated?: (payload: {\n document: Corti.DocumentsGetResponse;\n }) => void;\n onDocumentUpdated?: (payload: {\n document: Corti.DocumentsGetResponse;\n }) => void;\n onNavigationChanged?: (payload: any) => void;\n onUsage?: (payload: EmbeddedEventData['usage']) => void;\n onError?: (error: {\n message: string;\n code?: string;\n details?: unknown;\n }) => void;\n}\n\nexport class PostMessageHandler {\n private pendingRequests = new Map<\n string,\n { resolve: (value: any) => void; reject: (reason: any) => void }\n >();\n\n private messageListener: ((event: MessageEvent) => void) | null = null;\n\n private iframe: HTMLIFrameElement;\n\n private isReady = false;\n\n private callbacks: PostMessageHandlerCallbacks;\n\n constructor(\n iframe: HTMLIFrameElement,\n callbacks: PostMessageHandlerCallbacks = {},\n ) {\n this.iframe = iframe;\n this.callbacks = callbacks;\n this.setupMessageListener();\n }\n\n private setupMessageListener() {\n this.messageListener = (event: MessageEvent) => {\n // Only handle messages from our iframe\n if (event.source !== this.iframe.contentWindow) {\n return;\n }\n\n // Enforce origin to match the trusted iframe origin\n const trustedOrigin = this.getTrustedOrigin();\n if (!trustedOrigin || event.origin !== trustedOrigin) {\n return;\n }\n\n const { data } = event;\n\n // Check for Corti embedded events\n if (data?.type === 'CORTI_EMBEDDED_EVENT') {\n this.handleEvent(data);\n return;\n }\n\n // Check if this is a response to a pending request\n if (data.requestId && this.pendingRequests.has(data.requestId)) {\n this.handleResponse(data);\n }\n };\n\n window.addEventListener('message', this.messageListener);\n }\n\n private handleEvent(eventData: AnyEmbeddedEvent): void {\n const eventType = (eventData as any).event;\n const { payload } = eventData;\n\n // Handle ready-like events\n if (eventType === 'ready' || eventType === 'loaded') {\n this.isReady = true;\n }\n\n // Handle specific events with callbacks\n switch (eventType) {\n case 'ready':\n case 'loaded':\n this.callbacks.onReady?.();\n break;\n case 'authChanged':\n this.callbacks.onAuthChanged?.(payload);\n break;\n case 'interactionCreated':\n this.callbacks.onInteractionCreated?.(payload);\n break;\n case 'recordingStarted':\n this.callbacks.onRecordingStarted?.();\n break;\n case 'recordingStopped':\n this.callbacks.onRecordingStopped?.();\n break;\n case 'documentGenerated':\n this.callbacks.onDocumentGenerated?.(\n payload as EmbeddedEventData['document-generated'],\n );\n break;\n case 'documentUpdated':\n this.callbacks.onDocumentUpdated?.(\n payload as EmbeddedEventData['document-updated'],\n );\n break;\n case 'navigationChanged':\n this.callbacks.onNavigationChanged?.(payload);\n break;\n case 'usage':\n this.callbacks.onUsage?.(payload as EmbeddedEventData['usage']);\n break;\n default:\n console.warn(`Unhandled event type: ${eventType}`);\n break;\n }\n }\n\n private handleResponse(data: any): void {\n const pendingRequest = this.pendingRequests.get(data.requestId);\n if (pendingRequest) {\n const { resolve, reject } = pendingRequest;\n this.pendingRequests.delete(data.requestId);\n\n if (data.success === false || data.error) {\n const error = {\n message: data.error || 'Request failed',\n code: data.errorCode,\n details: data.errorDetails,\n };\n this.callbacks.onError?.(error);\n reject(error);\n } else {\n resolve(data);\n }\n }\n }\n\n destroy() {\n if (this.messageListener) {\n window.removeEventListener('message', this.messageListener);\n this.messageListener = null;\n }\n this.pendingRequests.clear();\n }\n\n /**\n * Update callbacks after construction\n */\n updateCallbacks(callbacks: PostMessageHandlerCallbacks) {\n this.callbacks = { ...this.callbacks, ...callbacks };\n }\n\n /**\n * Check if the iframe is ready to receive postMessages\n */\n get ready(): boolean {\n return this.isReady;\n }\n\n /**\n * Wait for the iframe to be ready\n * @param timeout - Optional timeout in milliseconds (default: 30000ms)\n * @returns Promise that resolves when ready\n */\n async waitForReady(timeout = 30000): Promise<void> {\n if (this.isReady) {\n return Promise.resolve();\n }\n\n return new Promise((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n reject(new Error('Timeout waiting for iframe to be ready'));\n }, timeout);\n\n // Create a one-time listener for the ready event\n const readyListener = (event: MessageEvent) => {\n if (\n event.source === this.iframe.contentWindow &&\n event.origin === this.getTrustedOrigin() &&\n event.data?.type === 'CORTI_EMBEDDED_EVENT' &&\n event.data.event === 'ready'\n ) {\n clearTimeout(timeoutId);\n window.removeEventListener('message', readyListener);\n resolve();\n }\n };\n\n window.addEventListener('message', readyListener);\n });\n }\n\n /**\n * Sends a postMessage to the iframe and returns a Promise that resolves with the response\n * @param message - The message to send\n * @param timeout - Optional timeout in milliseconds (default: 10000ms)\n * @returns Promise that resolves with the response\n */\n async postMessage(\n message: Omit<EmbeddedRequest, 'requestId'>,\n timeout = 10000,\n ): Promise<EmbeddedResponse> {\n if (!this.iframe.contentWindow) {\n throw new Error('Iframe not ready');\n }\n\n // Ensure the iframe has signaled readiness before sending\n await this.waitForReady();\n\n const { contentWindow } = this.iframe;\n const requestId = PostMessageHandler.generateRequestId();\n\n return new Promise((resolve, reject) => {\n // Set up timeout\n const timeoutId = setTimeout(() => {\n this.pendingRequests.delete(requestId);\n reject(new Error('Request timeout'));\n }, timeout);\n\n // Store the promise handlers\n const handlers = {\n resolve: (value: any) => {\n clearTimeout(timeoutId);\n resolve(value);\n },\n reject: (reason: any) => {\n clearTimeout(timeoutId);\n reject(reason);\n },\n };\n\n this.pendingRequests.set(requestId, handlers);\n\n // Send the message\n const fullMessage: EmbeddedRequest = {\n ...message,\n requestId,\n };\n\n const targetOrigin = this.getTrustedOrigin();\n if (!targetOrigin) {\n this.pendingRequests.delete(requestId);\n reject(new Error('Cannot determine trusted origin for postMessage'));\n return;\n }\n contentWindow.postMessage(fullMessage, targetOrigin);\n });\n }\n\n /**\n * Helper method to send an auth message and return clean user data\n * @param payload - Auth payload\n * @returns Promise that resolves with user data\n */\n async auth(payload: AuthPayload): Promise<AuthResponse['user']> {\n const response = await this.postMessage({\n type: 'CORTI_EMBEDDED',\n version: 'v1',\n action: 'auth',\n payload,\n });\n this.isReady = false;\n\n if (response.payload && typeof response.success) {\n return (response.payload as AuthResponse).user;\n }\n throw new Error(response.error);\n }\n\n /**\n * Helper method to configure a session\n * @param payload - Session configuration payload\n * @returns Promise that resolves when configuration is complete\n */\n async configureSession(payload: ConfigureSessionPayload): Promise<void> {\n await this.postMessage({\n type: 'CORTI_EMBEDDED',\n version: 'v1',\n action: 'configureSession',\n payload,\n });\n }\n\n /**\n * Helper method to navigate to a specific path\n * @param payload - Navigation payload\n * @returns Promise that resolves when navigation is complete\n */\n async navigate(payload: NavigatePayload): Promise<void> {\n await this.postMessage({\n type: 'CORTI_EMBEDDED',\n version: 'v1',\n action: 'navigate',\n payload,\n });\n }\n\n /**\n * Helper method to add facts to the session\n * @param payload - Facts payload\n * @returns Promise that resolves when facts are added\n */\n async addFacts(payload: AddFactsPayload): Promise<void> {\n await this.postMessage({\n type: 'CORTI_EMBEDDED',\n version: 'v1',\n action: 'addFacts',\n payload,\n });\n }\n\n /**\n * Helper method to create a new interaction and return clean interaction data\n * @param payload - Interaction creation payload\n * @returns Promise that resolves with interaction details\n */\n async createInteraction(\n payload: InteractionPayload,\n ): Promise<CreateInteractionResponse> {\n const response = await this.postMessage({\n type: 'CORTI_EMBEDDED',\n version: 'v1',\n action: 'createInteraction',\n payload,\n });\n\n if (response.payload && typeof response.success) {\n return response.payload as CreateInteractionResponse;\n }\n throw new Error(response.error);\n }\n\n /**\n * Helper method to start recording\n * @returns Promise that resolves when recording starts\n */\n async startRecording(): Promise<void> {\n await this.postMessage({\n type: 'CORTI_EMBEDDED',\n version: 'v1',\n action: 'startRecording',\n payload: {},\n });\n }\n\n /**\n * Helper method to stop recording\n * @returns Promise that resolves when recording stops\n */\n async stopRecording(): Promise<void> {\n await this.postMessage({\n type: 'CORTI_EMBEDDED',\n version: 'v1',\n action: 'stopRecording',\n payload: {},\n });\n }\n\n /**\n * Helper method to get current status\n * @returns Promise that resolves with current status\n */\n async getStatus(): Promise<GetStatusResponse> {\n const response = await this.postMessage({\n type: 'CORTI_EMBEDDED',\n version: 'v1',\n action: 'getStatus',\n payload: {},\n });\n\n if (response.payload && typeof response.success) {\n return response.payload as GetStatusResponse;\n }\n throw new Error(response.error);\n }\n\n /**\n * Helper method to configure the component\n * @param payload - Component configuration payload\n * @returns Promise that resolves when configuration is applied\n */\n async configure(\n payload: ConfigureAppPayload,\n ): Promise<ConfigureAppResponsePayload> {\n const response = await this.postMessage({\n type: 'CORTI_EMBEDDED',\n version: 'v1',\n action: 'configure',\n payload,\n });\n\n if (response.payload && typeof response.success) {\n return response.payload as ConfigureAppResponsePayload;\n }\n throw new Error(response.error);\n }\n\n /**\n * Helper method to set credentials without triggering auth flow\n * @param payload - Credentials payload\n * @returns Promise that resolves when credentials are set\n */\n async setCredentials(payload: SetCredentialsPayload): Promise<void> {\n await this.postMessage({\n type: 'CORTI_EMBEDDED',\n version: 'v1',\n action: 'setCredentials',\n payload,\n });\n }\n\n private static generateRequestId(): string {\n return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;\n }\n\n /**\n * Derive the trusted origin from the iframe src (constructed from baseURL).\n * Returns null if it cannot be determined.\n */\n private getTrustedOrigin(): string | null {\n try {\n // If iframe.src is relative, URL() will resolve against the current location.\n // Embeds should provide an absolute baseURL; enforcing strict origin here.\n const src = this.iframe.getAttribute('src') || this.iframe.src;\n if (!src) return null;\n const url = new URL(src, window.location.href);\n return url.origin;\n } catch {\n return null;\n }\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function validateAndNormalizeBaseURL(url: string): string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export function validateAndNormalizeBaseURL(url) {
|
|
2
|
+
let parsed;
|
|
3
|
+
try {
|
|
4
|
+
parsed = new URL(url);
|
|
5
|
+
}
|
|
6
|
+
catch {
|
|
7
|
+
throw new Error('Invalid baseURL: not a parseable URL');
|
|
8
|
+
}
|
|
9
|
+
if (parsed.protocol !== 'https:') {
|
|
10
|
+
throw new Error('Invalid baseURL: must use https');
|
|
11
|
+
}
|
|
12
|
+
const host = parsed.host.toLowerCase();
|
|
13
|
+
const pattern = /^assistant\.[a-z0-9-]+\.corti\.app$/i;
|
|
14
|
+
if (!pattern.test(host)) {
|
|
15
|
+
throw new Error('Invalid baseURL: host must match assistant.xxx.corti.app');
|
|
16
|
+
}
|
|
17
|
+
if (parsed.pathname && parsed.pathname !== '/' && parsed.pathname !== '') {
|
|
18
|
+
throw new Error('Invalid baseURL: must not include a path');
|
|
19
|
+
}
|
|
20
|
+
if (parsed.username || parsed.password) {
|
|
21
|
+
throw new Error('Invalid baseURL: must not include credentials');
|
|
22
|
+
}
|
|
23
|
+
return parsed.origin.replace(/\/+$/, '');
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=baseUrl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseUrl.js","sourceRoot":"","sources":["../../src/utils/baseUrl.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,2BAA2B,CAAC,GAAW;IACrD,IAAI,MAAW,CAAC;IAChB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAC1D,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;IACrD,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;IACvC,MAAM,OAAO,GAAG,sCAAsC,CAAC;IACvD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAC;IAC9E,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,QAAQ,KAAK,GAAG,IAAI,MAAM,CAAC,QAAQ,KAAK,EAAE,EAAE,CAAC;QACzE,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;IAC9D,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACvC,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAC3C,CAAC","sourcesContent":["export function validateAndNormalizeBaseURL(url: string): string {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new Error('Invalid baseURL: not a parseable URL');\n }\n if (parsed.protocol !== 'https:') {\n throw new Error('Invalid baseURL: must use https');\n }\n const host = parsed.host.toLowerCase();\n const pattern = /^assistant\\.[a-z0-9-]+\\.corti\\.app$/i;\n if (!pattern.test(host)) {\n throw new Error('Invalid baseURL: host must match assistant.xxx.corti.app');\n }\n if (parsed.pathname && parsed.pathname !== '/' && parsed.pathname !== '') {\n throw new Error('Invalid baseURL: must not include a path');\n }\n if (parsed.username || parsed.password) {\n throw new Error('Invalid baseURL: must not include credentials');\n }\n return parsed.origin.replace(/\\/+$/, '');\n}\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function buildEmbeddedUrl(normalizedBaseURL) {
|
|
2
|
+
return `${normalizedBaseURL}/embedded`;
|
|
3
|
+
}
|
|
4
|
+
export function isRealEmbeddedLoad(src, normalizedBaseURL) {
|
|
5
|
+
if (!src || src.startsWith('about:')) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
try {
|
|
9
|
+
const srcUrl = new URL(src, window.location.href);
|
|
10
|
+
const baseUrl = new URL(normalizedBaseURL);
|
|
11
|
+
// Require exact origin match
|
|
12
|
+
if (srcUrl.origin !== baseUrl.origin) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
// Accept /embedded with optional trailing slash; allow query/hash
|
|
16
|
+
const normalizedPath = srcUrl.pathname.replace(/\/+$/, '');
|
|
17
|
+
return normalizedPath === '/embedded';
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=embedUrl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedUrl.js","sourceRoot":"","sources":["../../src/utils/embedUrl.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,gBAAgB,CAAC,iBAAyB;IACxD,OAAO,GAAG,iBAAiB,WAAW,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,kBAAkB,CAChC,GAAW,EACX,iBAAyB;IAEzB,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QACrC,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAClD,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,iBAAiB,CAAC,CAAC;QAC3C,6BAA6B;QAC7B,IAAI,MAAM,CAAC,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC;YACrC,OAAO,KAAK,CAAC;QACf,CAAC;QACD,kEAAkE;QAClE,MAAM,cAAc,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAC3D,OAAO,cAAc,KAAK,WAAW,CAAC;IACxC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC","sourcesContent":["export function buildEmbeddedUrl(normalizedBaseURL: string): string {\n return `${normalizedBaseURL}/embedded`;\n}\n\nexport function isRealEmbeddedLoad(\n src: string,\n normalizedBaseURL: string,\n): boolean {\n if (!src || src.startsWith('about:')) {\n return false;\n }\n try {\n const srcUrl = new URL(src, window.location.href);\n const baseUrl = new URL(normalizedBaseURL);\n // Require exact origin match\n if (srcUrl.origin !== baseUrl.origin) {\n return false;\n }\n // Accept /embedded with optional trailing slash; allow query/hash\n const normalizedPath = srcUrl.pathname.replace(/\\/+$/, '');\n return normalizedPath === '/embedded';\n } catch {\n return false;\n }\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
interface FormattedError {
|
|
2
|
+
message: string;
|
|
3
|
+
code?: string;
|
|
4
|
+
details?: unknown;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Generic error formatter that handles various error formats
|
|
8
|
+
*/
|
|
9
|
+
export declare function formatError(error: unknown, fallbackMessage?: string): FormattedError;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attempts to parse a JSON string safely
|
|
3
|
+
*/
|
|
4
|
+
function tryParseJson(str) {
|
|
5
|
+
try {
|
|
6
|
+
return JSON.parse(str);
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Extracts a 3-digit HTTP status code from an error message
|
|
14
|
+
*/
|
|
15
|
+
function extractStatusCode(message) {
|
|
16
|
+
// Match 3-digit numbers that look like HTTP status codes (100-599)
|
|
17
|
+
const statusCodeMatch = message.match(/\b([1-5]\d{2})\b/);
|
|
18
|
+
return statusCodeMatch ? statusCodeMatch[1] : undefined;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Checks if an object looks like a validation error
|
|
22
|
+
*/
|
|
23
|
+
function isValidationError(obj) {
|
|
24
|
+
return (typeof obj === 'object' &&
|
|
25
|
+
obj !== null &&
|
|
26
|
+
('expected' in obj || 'code' in obj || 'path' in obj || 'message' in obj));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Formats a validation error into a human-readable message
|
|
30
|
+
*/
|
|
31
|
+
function formatValidationError(error) {
|
|
32
|
+
const { path, expected, message, code } = error;
|
|
33
|
+
// Use the provided message if available
|
|
34
|
+
if (message) {
|
|
35
|
+
return message;
|
|
36
|
+
}
|
|
37
|
+
// Build a descriptive message from the parts
|
|
38
|
+
const pathString = path && path.length > 0 ? path.join('.') : 'field';
|
|
39
|
+
if (expected) {
|
|
40
|
+
return `Invalid ${pathString}: expected ${expected}`;
|
|
41
|
+
}
|
|
42
|
+
if (code) {
|
|
43
|
+
return `Validation error in ${pathString} (${code})`;
|
|
44
|
+
}
|
|
45
|
+
return `Validation error in ${pathString}`;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Formats multiple validation errors into a single message
|
|
49
|
+
*/
|
|
50
|
+
function formatValidationErrors(errors) {
|
|
51
|
+
if (errors.length === 0) {
|
|
52
|
+
return 'Unknown validation error';
|
|
53
|
+
}
|
|
54
|
+
if (errors.length === 1) {
|
|
55
|
+
return formatValidationError(errors[0]);
|
|
56
|
+
}
|
|
57
|
+
const errorMessages = errors.map(formatValidationError);
|
|
58
|
+
return `Multiple validation errors: ${errorMessages.join('; ')}`;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Generic error formatter that handles various error formats
|
|
62
|
+
*/
|
|
63
|
+
export function formatError(error, fallbackMessage = 'An error occurred') {
|
|
64
|
+
// Handle null/undefined
|
|
65
|
+
if (!error) {
|
|
66
|
+
return {
|
|
67
|
+
message: fallbackMessage,
|
|
68
|
+
details: error,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
// Handle Error objects
|
|
72
|
+
if (error instanceof Error) {
|
|
73
|
+
const extractedCode = extractStatusCode(error.message);
|
|
74
|
+
return {
|
|
75
|
+
message: error.message || fallbackMessage,
|
|
76
|
+
code: extractedCode,
|
|
77
|
+
details: error,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
// Handle string errors (like "400 Bad Request")
|
|
81
|
+
if (typeof error === 'string') {
|
|
82
|
+
const extractedCode = extractStatusCode(error);
|
|
83
|
+
return {
|
|
84
|
+
message: error,
|
|
85
|
+
code: extractedCode,
|
|
86
|
+
details: error,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
// Handle arrays (direct validation error arrays)
|
|
90
|
+
if (Array.isArray(error)) {
|
|
91
|
+
// Check if it's an array of validation errors
|
|
92
|
+
if (error.every(isValidationError)) {
|
|
93
|
+
const validationErrors = error;
|
|
94
|
+
return {
|
|
95
|
+
message: formatValidationErrors(validationErrors),
|
|
96
|
+
code: validationErrors[0]?.code, // Use the first error's code if available
|
|
97
|
+
details: error,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
// Handle other arrays
|
|
101
|
+
return {
|
|
102
|
+
message: `Multiple errors: ${error.length} item(s)`,
|
|
103
|
+
details: error,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
// Handle error objects
|
|
107
|
+
if (typeof error === 'object') {
|
|
108
|
+
const errorObj = error;
|
|
109
|
+
// Handle objects with message and details structure (your original case)
|
|
110
|
+
if (errorObj.message && typeof errorObj.message === 'string') {
|
|
111
|
+
let formattedMessage = errorObj.message;
|
|
112
|
+
let { code } = errorObj;
|
|
113
|
+
// If no code is provided, try to extract it from the message
|
|
114
|
+
if (!code) {
|
|
115
|
+
code = extractStatusCode(formattedMessage);
|
|
116
|
+
}
|
|
117
|
+
// Try to enhance the message with parsed details
|
|
118
|
+
if (errorObj.details?.message &&
|
|
119
|
+
typeof errorObj.details.message === 'string') {
|
|
120
|
+
const parsedDetails = tryParseJson(errorObj.details.message);
|
|
121
|
+
// Check if parsed details is an array of validation errors
|
|
122
|
+
if (Array.isArray(parsedDetails) &&
|
|
123
|
+
parsedDetails.every(isValidationError)) {
|
|
124
|
+
formattedMessage = formatValidationErrors(parsedDetails);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
message: formattedMessage,
|
|
129
|
+
code,
|
|
130
|
+
details: errorObj.details,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
// Handle single validation error objects
|
|
134
|
+
if (isValidationError(errorObj)) {
|
|
135
|
+
return {
|
|
136
|
+
message: formatValidationError(errorObj),
|
|
137
|
+
code: errorObj.code,
|
|
138
|
+
details: error,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
// Handle objects that might have useful error info
|
|
142
|
+
if (errorObj.error || errorObj.message || errorObj.detail) {
|
|
143
|
+
const message = errorObj.error || errorObj.message || errorObj.detail;
|
|
144
|
+
const messageStr = typeof message === 'string' ? message : fallbackMessage;
|
|
145
|
+
// Try to get code from various properties, or extract from message
|
|
146
|
+
let code = errorObj.code || errorObj.status;
|
|
147
|
+
if (!code && typeof message === 'string') {
|
|
148
|
+
code = extractStatusCode(message);
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
message: messageStr,
|
|
152
|
+
code,
|
|
153
|
+
details: error,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Fallback for any other case
|
|
158
|
+
return {
|
|
159
|
+
message: fallbackMessage,
|
|
160
|
+
details: error,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=errorFormatter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errorFormatter.js","sourceRoot":"","sources":["../../src/utils/errorFormatter.ts"],"names":[],"mappings":"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","sourcesContent":["interface ValidationError {\n expected?: string;\n code?: string;\n path?: string[];\n message?: string;\n}\n\ninterface FormattedError {\n message: string;\n code?: string;\n details?: unknown;\n}\n\n/**\n * Attempts to parse a JSON string safely\n */\nfunction tryParseJson(str: string): unknown {\n try {\n return JSON.parse(str);\n } catch {\n return null;\n }\n}\n\n/**\n * Extracts a 3-digit HTTP status code from an error message\n */\nfunction extractStatusCode(message: string): string | undefined {\n // Match 3-digit numbers that look like HTTP status codes (100-599)\n const statusCodeMatch = message.match(/\\b([1-5]\\d{2})\\b/);\n return statusCodeMatch ? statusCodeMatch[1] : undefined;\n}\n\n/**\n * Checks if an object looks like a validation error\n */\nfunction isValidationError(obj: unknown): obj is ValidationError {\n return (\n typeof obj === 'object' &&\n obj !== null &&\n ('expected' in obj || 'code' in obj || 'path' in obj || 'message' in obj)\n );\n}\n\n/**\n * Formats a validation error into a human-readable message\n */\nfunction formatValidationError(error: ValidationError): string {\n const { path, expected, message, code } = error;\n\n // Use the provided message if available\n if (message) {\n return message;\n }\n\n // Build a descriptive message from the parts\n const pathString = path && path.length > 0 ? path.join('.') : 'field';\n\n if (expected) {\n return `Invalid ${pathString}: expected ${expected}`;\n }\n\n if (code) {\n return `Validation error in ${pathString} (${code})`;\n }\n\n return `Validation error in ${pathString}`;\n}\n\n/**\n * Formats multiple validation errors into a single message\n */\nfunction formatValidationErrors(errors: ValidationError[]): string {\n if (errors.length === 0) {\n return 'Unknown validation error';\n }\n\n if (errors.length === 1) {\n return formatValidationError(errors[0]);\n }\n\n const errorMessages = errors.map(formatValidationError);\n return `Multiple validation errors: ${errorMessages.join('; ')}`;\n}\n\n/**\n * Generic error formatter that handles various error formats\n */\nexport function formatError(\n error: unknown,\n fallbackMessage = 'An error occurred',\n): FormattedError {\n // Handle null/undefined\n if (!error) {\n return {\n message: fallbackMessage,\n details: error,\n };\n }\n\n // Handle Error objects\n if (error instanceof Error) {\n const extractedCode = extractStatusCode(error.message);\n return {\n message: error.message || fallbackMessage,\n code: extractedCode,\n details: error,\n };\n }\n\n // Handle string errors (like \"400 Bad Request\")\n if (typeof error === 'string') {\n const extractedCode = extractStatusCode(error);\n return {\n message: error,\n code: extractedCode,\n details: error,\n };\n }\n\n // Handle arrays (direct validation error arrays)\n if (Array.isArray(error)) {\n // Check if it's an array of validation errors\n if (error.every(isValidationError)) {\n const validationErrors = error as ValidationError[];\n return {\n message: formatValidationErrors(validationErrors),\n code: validationErrors[0]?.code, // Use the first error's code if available\n details: error,\n };\n }\n\n // Handle other arrays\n return {\n message: `Multiple errors: ${error.length} item(s)`,\n details: error,\n };\n }\n\n // Handle error objects\n if (typeof error === 'object') {\n const errorObj = error as any;\n\n // Handle objects with message and details structure (your original case)\n if (errorObj.message && typeof errorObj.message === 'string') {\n let formattedMessage = errorObj.message;\n let { code } = errorObj;\n\n // If no code is provided, try to extract it from the message\n if (!code) {\n code = extractStatusCode(formattedMessage);\n }\n\n // Try to enhance the message with parsed details\n if (\n errorObj.details?.message &&\n typeof errorObj.details.message === 'string'\n ) {\n const parsedDetails = tryParseJson(errorObj.details.message);\n\n // Check if parsed details is an array of validation errors\n if (\n Array.isArray(parsedDetails) &&\n parsedDetails.every(isValidationError)\n ) {\n formattedMessage = formatValidationErrors(\n parsedDetails as ValidationError[],\n );\n }\n }\n\n return {\n message: formattedMessage,\n code,\n details: errorObj.details,\n };\n }\n\n // Handle single validation error objects\n if (isValidationError(errorObj)) {\n return {\n message: formatValidationError(errorObj),\n code: errorObj.code,\n details: error,\n };\n }\n\n // Handle objects that might have useful error info\n if (errorObj.error || errorObj.message || errorObj.detail) {\n const message = errorObj.error || errorObj.message || errorObj.detail;\n const messageStr =\n typeof message === 'string' ? message : fallbackMessage;\n\n // Try to get code from various properties, or extract from message\n let code = errorObj.code || errorObj.status;\n if (!code && typeof message === 'string') {\n code = extractStatusCode(message);\n }\n\n return {\n message: messageStr,\n code,\n details: error,\n };\n }\n }\n\n // Fallback for any other case\n return {\n message: fallbackMessage,\n details: error,\n };\n}\n"]}
|