@fluid-app/portal-sdk 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +415 -0
- package/dist/ContactsScreen-CB6l0Lf1.mjs +24 -0
- package/dist/ContactsScreen-CB6l0Lf1.mjs.map +1 -0
- package/dist/ContactsScreen-Cqyrl4AQ.cjs +41 -0
- package/dist/ContactsScreen-Cqyrl4AQ.cjs.map +1 -0
- package/dist/CoreScreenPlaceholder-CA2-2V5o.cjs +38 -0
- package/dist/CoreScreenPlaceholder-CA2-2V5o.cjs.map +1 -0
- package/dist/CoreScreenPlaceholder-D93ZYKt2.mjs +32 -0
- package/dist/CoreScreenPlaceholder-D93ZYKt2.mjs.map +1 -0
- package/dist/CustomersScreen-BEar6Leg.mjs +24 -0
- package/dist/CustomersScreen-BEar6Leg.mjs.map +1 -0
- package/dist/CustomersScreen-gFgOzBox.cjs +41 -0
- package/dist/CustomersScreen-gFgOzBox.cjs.map +1 -0
- package/dist/MessagingScreen-BklF48Fn.mjs +1281 -0
- package/dist/MessagingScreen-BklF48Fn.mjs.map +1 -0
- package/dist/MessagingScreen-CHb-scHD.cjs +1454 -0
- package/dist/MessagingScreen-CHb-scHD.cjs.map +1 -0
- package/dist/OrdersScreen-CPGDYvEd.cjs +41 -0
- package/dist/OrdersScreen-CPGDYvEd.cjs.map +1 -0
- package/dist/OrdersScreen-DB1v051q.mjs +24 -0
- package/dist/OrdersScreen-DB1v051q.mjs.map +1 -0
- package/dist/ProductsScreen-B1tlIth6.cjs +41 -0
- package/dist/ProductsScreen-B1tlIth6.cjs.map +1 -0
- package/dist/ProductsScreen-nVDsY6kf.mjs +24 -0
- package/dist/ProductsScreen-nVDsY6kf.mjs.map +1 -0
- package/dist/chunk-D1SwGrFN.mjs +27 -0
- package/dist/index.cjs +3610 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2500 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +2498 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2921 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +76 -0
|
@@ -0,0 +1,1281 @@
|
|
|
1
|
+
import { n as __reExport, t as __exportAll } from "./chunk-D1SwGrFN.mjs";
|
|
2
|
+
import { createContext, useCallback, useContext, useEffect, useMemo, useReducer, useRef, useState } from "react";
|
|
3
|
+
import { QueryClient, QueryClientProvider, useQuery } from "@tanstack/react-query";
|
|
4
|
+
import { AUTH_REDIRECT_TOKEN_KEY, DEFAULT_AUTH_URL, cleanTokenFromUrl, clearTokens, createDefaultAuthRedirect, createDevUser, extractTokenFromUrl, getStoredToken, isDevBypassActive, isTokenExpired, resolveAuthFailureHandler, storeToken, validateToken, verifyToken } from "@fluid-app/auth";
|
|
5
|
+
import { buildThemeDefinition, getActiveThemeId, getActiveThemeId as getActiveThemeId$1, transformThemes, transformThemes as transformThemes$1 } from "@fluid-app/portal-core/theme";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { DataSourceRegistryProvider } from "@fluid-app/portal-core/data-sources/registry-context";
|
|
8
|
+
import { RegistryProvider } from "@fluid-app/portal-widgets/contexts";
|
|
9
|
+
import { createWidgetRegistry } from "@fluid-app/portal-core/widget-utils";
|
|
10
|
+
import { AlertWidget, CalendarWidget, CarouselWidget, CatchUpWidget, ChartWidget, ContainerWidget, EmbedWidget, ImageWidget, LayoutWidget, ListWidget, MySiteWidget, NestedWidget, QuickShareWidget, RecentActivityWidget, SpacerWidget, TableWidget, TextWidget, ToDoWidget, VideoWidget } from "@fluid-app/portal-widgets/widgets";
|
|
11
|
+
import { getFileTypeFromMimetype } from "@fluid-app/messaging-core";
|
|
12
|
+
import { MessagingApp } from "@fluid-app/messaging-ui/app";
|
|
13
|
+
import { extractEmoji, formatMessageChannelName } from "@fluid-app/messaging-ui";
|
|
14
|
+
import { listConnectedRecipients, searchConversations } from "@fluid-app/messaging-api-client";
|
|
15
|
+
//#region ../../platform/api-client-core/src/fetch-client.ts
|
|
16
|
+
/**
|
|
17
|
+
* API Error class compatible with fluid-admin's ApiError
|
|
18
|
+
*/
|
|
19
|
+
var ApiError$1 = class ApiError$1 extends Error {
|
|
20
|
+
status;
|
|
21
|
+
data;
|
|
22
|
+
constructor(message, status, data) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.name = "ApiError";
|
|
25
|
+
this.status = status;
|
|
26
|
+
this.data = data;
|
|
27
|
+
if ("captureStackTrace" in Error) Error.captureStackTrace(this, ApiError$1);
|
|
28
|
+
}
|
|
29
|
+
toJSON() {
|
|
30
|
+
return {
|
|
31
|
+
name: this.name,
|
|
32
|
+
message: this.message,
|
|
33
|
+
status: this.status,
|
|
34
|
+
data: this.data
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Creates a configured fetch client instance
|
|
40
|
+
*/
|
|
41
|
+
function createFetchClient(config) {
|
|
42
|
+
const { baseUrl, getAuthToken, onAuthError, defaultHeaders = {} } = config;
|
|
43
|
+
/**
|
|
44
|
+
* Build headers for a request
|
|
45
|
+
*/
|
|
46
|
+
async function buildHeaders(customHeaders) {
|
|
47
|
+
const headers = {
|
|
48
|
+
Accept: "application/json",
|
|
49
|
+
"Content-Type": "application/json",
|
|
50
|
+
...defaultHeaders,
|
|
51
|
+
...customHeaders
|
|
52
|
+
};
|
|
53
|
+
if (getAuthToken) {
|
|
54
|
+
const token = await getAuthToken();
|
|
55
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
56
|
+
}
|
|
57
|
+
return headers;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Join baseUrl + endpoint via string concatenation (matches fetchApi).
|
|
61
|
+
* Using `new URL(endpoint, baseUrl)` would strip any path prefix from
|
|
62
|
+
* baseUrl (e.g. "/api") when the endpoint starts with "/".
|
|
63
|
+
*/
|
|
64
|
+
function joinUrl(endpoint) {
|
|
65
|
+
return `${baseUrl}${endpoint}`;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Build URL with query parameters for GET requests
|
|
69
|
+
* Compatible with fluid-admin's query param handling
|
|
70
|
+
*/
|
|
71
|
+
function buildUrl(endpoint, params) {
|
|
72
|
+
const fullUrl = joinUrl(endpoint);
|
|
73
|
+
if (!params || Object.keys(params).length === 0) return fullUrl;
|
|
74
|
+
const queryString = new URLSearchParams();
|
|
75
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
76
|
+
if (value === void 0 || value === null) return;
|
|
77
|
+
if (Array.isArray(value)) value.forEach((item) => queryString.append(`${key}[]`, String(item)));
|
|
78
|
+
else if (typeof value === "object") Object.entries(value).forEach(([subKey, subValue]) => {
|
|
79
|
+
if (subValue === void 0 || subValue === null) return;
|
|
80
|
+
if (Array.isArray(subValue)) subValue.forEach((item) => queryString.append(`${key}[${subKey}][]`, String(item)));
|
|
81
|
+
else queryString.append(`${key}[${subKey}]`, String(subValue));
|
|
82
|
+
});
|
|
83
|
+
else queryString.append(key, String(value));
|
|
84
|
+
});
|
|
85
|
+
const qs = queryString.toString();
|
|
86
|
+
return qs ? `${fullUrl}?${qs}` : fullUrl;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Shared response handler for both JSON and FormData requests.
|
|
90
|
+
* Handles auth errors, non-OK responses, 204 No Content, and JSON parsing.
|
|
91
|
+
*/
|
|
92
|
+
async function handleResponse(response, method, _url) {
|
|
93
|
+
if (response.status === 401 && onAuthError) onAuthError();
|
|
94
|
+
if (!response.ok) try {
|
|
95
|
+
if (response.headers.get("content-type")?.includes("application/json")) {
|
|
96
|
+
const data = await response.json();
|
|
97
|
+
throw new ApiError$1(data.message || data.error_message || `${method} request failed`, response.status, data.errors || data);
|
|
98
|
+
} else throw new ApiError$1(`${method} request failed with status ${response.status}`, response.status, null);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
if (error instanceof ApiError$1) throw error;
|
|
101
|
+
throw new ApiError$1(`${method} request failed with status ${response.status}`, response.status, null);
|
|
102
|
+
}
|
|
103
|
+
if (response.status === 204 || response.headers.get("content-length") === "0") return null;
|
|
104
|
+
if (response.headers.get("content-type")?.includes("application/json")) try {
|
|
105
|
+
return await response.json();
|
|
106
|
+
} catch {
|
|
107
|
+
try {
|
|
108
|
+
return await response.text();
|
|
109
|
+
} catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Main request function
|
|
117
|
+
*/
|
|
118
|
+
async function request(endpoint, options = {}) {
|
|
119
|
+
const { method = "GET", headers: customHeaders, params, body, signal } = options;
|
|
120
|
+
const url = params ? buildUrl(endpoint, params) : joinUrl(endpoint);
|
|
121
|
+
const headers = await buildHeaders(customHeaders);
|
|
122
|
+
let response;
|
|
123
|
+
try {
|
|
124
|
+
const fetchOptions = {
|
|
125
|
+
method,
|
|
126
|
+
headers
|
|
127
|
+
};
|
|
128
|
+
const serializedBody = body && method !== "GET" ? JSON.stringify(body) : null;
|
|
129
|
+
if (serializedBody) fetchOptions.body = serializedBody;
|
|
130
|
+
if (signal) fetchOptions.signal = signal;
|
|
131
|
+
response = await fetch(url, fetchOptions);
|
|
132
|
+
} catch (networkError) {
|
|
133
|
+
throw new ApiError$1(`Network error: ${networkError instanceof Error ? networkError.message : "Unknown network error"}`, 0, null);
|
|
134
|
+
}
|
|
135
|
+
return handleResponse(response, method, url);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Request with FormData (for file uploads)
|
|
139
|
+
*/
|
|
140
|
+
async function requestWithFormData(endpoint, formData, options = {}) {
|
|
141
|
+
const { method = "POST", headers: customHeaders, signal } = options;
|
|
142
|
+
const url = joinUrl(endpoint);
|
|
143
|
+
const headers = await buildHeaders(customHeaders);
|
|
144
|
+
delete headers["Content-Type"];
|
|
145
|
+
let response;
|
|
146
|
+
try {
|
|
147
|
+
const fetchOptions = {
|
|
148
|
+
method,
|
|
149
|
+
headers,
|
|
150
|
+
body: formData
|
|
151
|
+
};
|
|
152
|
+
if (signal) fetchOptions.signal = signal;
|
|
153
|
+
response = await fetch(url, fetchOptions);
|
|
154
|
+
} catch (networkError) {
|
|
155
|
+
throw new ApiError$1(`Network error: ${networkError instanceof Error ? networkError.message : "Unknown network error"}`, 0, null);
|
|
156
|
+
}
|
|
157
|
+
return handleResponse(response, method, url);
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
request,
|
|
161
|
+
requestWithFormData,
|
|
162
|
+
get: (endpoint, params, options) => request(endpoint, {
|
|
163
|
+
...options,
|
|
164
|
+
method: "GET",
|
|
165
|
+
...params && { params }
|
|
166
|
+
}),
|
|
167
|
+
post: (endpoint, body, options) => request(endpoint, {
|
|
168
|
+
...options,
|
|
169
|
+
method: "POST",
|
|
170
|
+
body
|
|
171
|
+
}),
|
|
172
|
+
put: (endpoint, body, options) => request(endpoint, {
|
|
173
|
+
...options,
|
|
174
|
+
method: "PUT",
|
|
175
|
+
body
|
|
176
|
+
}),
|
|
177
|
+
patch: (endpoint, body, options) => request(endpoint, {
|
|
178
|
+
...options,
|
|
179
|
+
method: "PATCH",
|
|
180
|
+
body
|
|
181
|
+
}),
|
|
182
|
+
delete: (endpoint, options) => request(endpoint, {
|
|
183
|
+
...options,
|
|
184
|
+
method: "DELETE"
|
|
185
|
+
})
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
//#endregion
|
|
189
|
+
//#region ../../fluidos/api-client/src/namespaces/fluid_os.ts
|
|
190
|
+
/**
|
|
191
|
+
* Get active Fluid OS definition
|
|
192
|
+
* Retrieve the active Fluid OS definition manifest for a specific platform
|
|
193
|
+
*
|
|
194
|
+
* @param client - Fetch client instance
|
|
195
|
+
* @param params - params
|
|
196
|
+
*/
|
|
197
|
+
async function getFluidOSManifest(client, params) {
|
|
198
|
+
return client.get(`/api/fluid_os/definitions/active`, params);
|
|
199
|
+
}
|
|
200
|
+
//#endregion
|
|
201
|
+
//#region src/client/types.ts
|
|
202
|
+
/**
|
|
203
|
+
* HTTP methods supported by the API client.
|
|
204
|
+
* Use `as const` for literal type inference and type safety.
|
|
205
|
+
*/
|
|
206
|
+
const HTTP_METHODS = {
|
|
207
|
+
GET: "GET",
|
|
208
|
+
POST: "POST",
|
|
209
|
+
PUT: "PUT",
|
|
210
|
+
PATCH: "PATCH",
|
|
211
|
+
DELETE: "DELETE"
|
|
212
|
+
};
|
|
213
|
+
//#endregion
|
|
214
|
+
//#region src/transforms/screen-transforms.ts
|
|
215
|
+
/**
|
|
216
|
+
* Normalize component_tree to always be an array.
|
|
217
|
+
* The API stores component_tree as a hash (object), but the frontend expects an array.
|
|
218
|
+
*/
|
|
219
|
+
function normalizeComponentTree(componentTree) {
|
|
220
|
+
if (!componentTree) return [];
|
|
221
|
+
if (Array.isArray(componentTree)) return componentTree;
|
|
222
|
+
if (typeof componentTree === "object") return [componentTree];
|
|
223
|
+
return [];
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Convert a raw FluidOS screen to ScreenDefinition.
|
|
227
|
+
* Normalizes component_tree and converts string IDs to numbers.
|
|
228
|
+
*/
|
|
229
|
+
function toScreenDefinition(screen) {
|
|
230
|
+
return {
|
|
231
|
+
id: Number(screen.id),
|
|
232
|
+
slug: screen.slug ?? "",
|
|
233
|
+
name: screen.name ?? "",
|
|
234
|
+
component_tree: normalizeComponentTree(screen.component_tree)
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
//#endregion
|
|
238
|
+
//#region src/transforms/navigation-transforms.ts
|
|
239
|
+
/**
|
|
240
|
+
* Convert a raw FluidOS navigation item to NavigationItem.
|
|
241
|
+
* Recursively transforms children and sorts by position.
|
|
242
|
+
*/
|
|
243
|
+
function toNavigationItem(item) {
|
|
244
|
+
const children = (item.children ?? []).map(toNavigationItem).sort((a, b) => (a.position ?? 0) - (b.position ?? 0));
|
|
245
|
+
return {
|
|
246
|
+
id: Number(item.id),
|
|
247
|
+
label: item.label ?? "Untitled",
|
|
248
|
+
...item.slug != null ? { slug: String(item.slug) } : {},
|
|
249
|
+
...item.icon != null ? { icon: String(item.icon) } : {},
|
|
250
|
+
...item.screen_id != null ? { screen_id: Number(item.screen_id) } : {},
|
|
251
|
+
...item.parent_id != null ? { parent_id: Number(item.parent_id) } : {},
|
|
252
|
+
...item.source != null ? { source: item.source } : {},
|
|
253
|
+
position: item.position ?? 0,
|
|
254
|
+
children
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
//#endregion
|
|
258
|
+
//#region src/transforms/index.ts
|
|
259
|
+
/**
|
|
260
|
+
* Transform a raw FluidOS manifest API response into RepAppData.
|
|
261
|
+
*
|
|
262
|
+
* This is the top-level transform used by `FluidClient.app.get()`.
|
|
263
|
+
* It handles:
|
|
264
|
+
* - Theme transformation (legacy and new formats)
|
|
265
|
+
* - Screen normalization (component_tree array wrapping)
|
|
266
|
+
* - Navigation item transformation (recursive with position sorting)
|
|
267
|
+
*/
|
|
268
|
+
function transformManifestToRepAppData(response) {
|
|
269
|
+
const manifest = response.manifest;
|
|
270
|
+
const rawProfile = manifest.profile;
|
|
271
|
+
const rawThemes = Array.isArray(rawProfile?.themes) ? rawProfile.themes : [];
|
|
272
|
+
const screens = (manifest.screens ?? []).map((screen) => toScreenDefinition(screen));
|
|
273
|
+
const navigationItems = (rawProfile?.navigation?.navigation_items ?? []).map(toNavigationItem);
|
|
274
|
+
const nav = rawProfile?.navigation;
|
|
275
|
+
const mobileNav = rawProfile?.mobile_navigation;
|
|
276
|
+
const mobileNavigationItems = (mobileNav?.navigation_items ?? []).map(toNavigationItem);
|
|
277
|
+
const activeThemeId = getActiveThemeId(rawThemes);
|
|
278
|
+
return {
|
|
279
|
+
definition_id: manifest.definition_id,
|
|
280
|
+
published_version: manifest.published_version ?? 0,
|
|
281
|
+
screens,
|
|
282
|
+
profile: {
|
|
283
|
+
name: rawProfile?.name ?? "Default",
|
|
284
|
+
definition_id: rawProfile?.definition_id ?? manifest.definition_id,
|
|
285
|
+
themes: transformThemes(rawThemes),
|
|
286
|
+
...activeThemeId !== void 0 ? { activeThemeId } : {},
|
|
287
|
+
navigation: {
|
|
288
|
+
definition_id: nav?.definition_id ?? manifest.definition_id,
|
|
289
|
+
id: nav?.id ?? 0,
|
|
290
|
+
name: nav?.name ?? "Main Navigation",
|
|
291
|
+
navigation_items: navigationItems,
|
|
292
|
+
screens
|
|
293
|
+
},
|
|
294
|
+
...mobileNav ? { mobile_navigation: {
|
|
295
|
+
definition_id: mobileNav.definition_id ?? manifest.definition_id,
|
|
296
|
+
id: mobileNav.id ?? 0,
|
|
297
|
+
name: mobileNav.name ?? "Mobile Navigation",
|
|
298
|
+
navigation_items: mobileNavigationItems,
|
|
299
|
+
screens
|
|
300
|
+
} } : {}
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
//#endregion
|
|
305
|
+
//#region src/client/fluid-client.ts
|
|
306
|
+
/**
|
|
307
|
+
* API Error class for structured error handling
|
|
308
|
+
*/
|
|
309
|
+
var ApiError = class ApiError extends Error {
|
|
310
|
+
status;
|
|
311
|
+
data;
|
|
312
|
+
constructor(message, status, data) {
|
|
313
|
+
super(message);
|
|
314
|
+
this.name = "ApiError";
|
|
315
|
+
this.status = status;
|
|
316
|
+
this.data = data;
|
|
317
|
+
const errorWithCapture = Error;
|
|
318
|
+
if (errorWithCapture.captureStackTrace) errorWithCapture.captureStackTrace(this, ApiError);
|
|
319
|
+
}
|
|
320
|
+
toJSON() {
|
|
321
|
+
return {
|
|
322
|
+
name: this.name,
|
|
323
|
+
message: this.message,
|
|
324
|
+
status: this.status,
|
|
325
|
+
data: this.data
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
/**
|
|
330
|
+
* Type guard for ApiError
|
|
331
|
+
*/
|
|
332
|
+
function isApiError(error) {
|
|
333
|
+
return error instanceof ApiError;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Type guard to check if a value is a non-null string
|
|
337
|
+
*/
|
|
338
|
+
function isString(value) {
|
|
339
|
+
return typeof value === "string";
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Extract error message from API response data using `in` operator narrowing.
|
|
343
|
+
* Checks common error message field names in order of precedence.
|
|
344
|
+
*/
|
|
345
|
+
function extractErrorMessage(data, fallback) {
|
|
346
|
+
if ("message" in data && isString(data.message)) return data.message;
|
|
347
|
+
if ("error_message" in data && isString(data.error_message)) return data.error_message;
|
|
348
|
+
if ("error" in data && isString(data.error)) return data.error;
|
|
349
|
+
return fallback;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Creates a configured Fluid API client instance
|
|
353
|
+
*/
|
|
354
|
+
function createFluidClient(config) {
|
|
355
|
+
const { baseUrl, getAuthToken, onAuthError, defaultHeaders = {} } = config;
|
|
356
|
+
const effectiveOnAuthError = onAuthError ?? createDefaultAuthRedirect();
|
|
357
|
+
const fetchClient = createFetchClient({
|
|
358
|
+
baseUrl,
|
|
359
|
+
...getAuthToken ? { getAuthToken } : {},
|
|
360
|
+
onAuthError: effectiveOnAuthError,
|
|
361
|
+
defaultHeaders
|
|
362
|
+
});
|
|
363
|
+
/**
|
|
364
|
+
* Build headers for a request
|
|
365
|
+
*/
|
|
366
|
+
async function buildHeaders(customHeaders) {
|
|
367
|
+
const headers = {
|
|
368
|
+
"Content-Type": "application/json",
|
|
369
|
+
...defaultHeaders,
|
|
370
|
+
...customHeaders
|
|
371
|
+
};
|
|
372
|
+
if (getAuthToken) {
|
|
373
|
+
const token = await getAuthToken();
|
|
374
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
375
|
+
}
|
|
376
|
+
return headers;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Build URL with query parameters (Rails-compatible)
|
|
380
|
+
*/
|
|
381
|
+
function buildUrl(endpoint, params) {
|
|
382
|
+
const normalizedBase = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
383
|
+
const normalizedEndpoint = endpoint.startsWith("/") ? endpoint : `/${endpoint}`;
|
|
384
|
+
const url = new URL(normalizedBase + normalizedEndpoint);
|
|
385
|
+
if (params) for (const [key, value] of Object.entries(params)) {
|
|
386
|
+
if (value === void 0 || value === null) continue;
|
|
387
|
+
if (Array.isArray(value)) for (const item of value) url.searchParams.append(`${key}[]`, String(item));
|
|
388
|
+
else if (typeof value === "object") for (const [subKey, subValue] of Object.entries(value)) {
|
|
389
|
+
if (subValue === void 0 || subValue === null) continue;
|
|
390
|
+
if (Array.isArray(subValue)) for (const item of subValue) url.searchParams.append(`${key}[${subKey}][]`, String(item));
|
|
391
|
+
else url.searchParams.append(`${key}[${subKey}]`, String(subValue));
|
|
392
|
+
}
|
|
393
|
+
else url.searchParams.append(key, String(value));
|
|
394
|
+
}
|
|
395
|
+
return url.toString();
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Default request options for type-safe defaults.
|
|
399
|
+
* Uses `satisfies` to validate against RequestOptions while preserving literal types.
|
|
400
|
+
*/
|
|
401
|
+
const defaultRequestOptions = { method: HTTP_METHODS.GET };
|
|
402
|
+
/**
|
|
403
|
+
* Main request function
|
|
404
|
+
*/
|
|
405
|
+
async function request(endpoint, options = {}) {
|
|
406
|
+
const { method = defaultRequestOptions.method, headers: customHeaders, params, body, signal } = options;
|
|
407
|
+
const url = buildUrl(endpoint, method === HTTP_METHODS.GET ? params : void 0);
|
|
408
|
+
const headers = await buildHeaders(customHeaders);
|
|
409
|
+
let response;
|
|
410
|
+
try {
|
|
411
|
+
const fetchOptions = {
|
|
412
|
+
method,
|
|
413
|
+
headers
|
|
414
|
+
};
|
|
415
|
+
if (signal !== void 0) fetchOptions.signal = signal;
|
|
416
|
+
if (body && method !== HTTP_METHODS.GET) fetchOptions.body = JSON.stringify(body);
|
|
417
|
+
response = await fetch(url, fetchOptions);
|
|
418
|
+
} catch (networkError) {
|
|
419
|
+
throw new ApiError(`Network error: ${networkError instanceof Error ? networkError.message : "Unknown network error"}`, 0, null);
|
|
420
|
+
}
|
|
421
|
+
if (response.status === 401) {
|
|
422
|
+
effectiveOnAuthError();
|
|
423
|
+
throw new ApiError("Authentication required", 401, null);
|
|
424
|
+
}
|
|
425
|
+
if (!response.ok) try {
|
|
426
|
+
if (response.headers.get("content-type")?.includes("application/json")) {
|
|
427
|
+
const data = await response.json();
|
|
428
|
+
throw new ApiError(extractErrorMessage(data, `${method} request failed`), response.status, "errors" in data ? data.errors : data);
|
|
429
|
+
} else throw new ApiError(`${method} request failed with status ${response.status}`, response.status, null);
|
|
430
|
+
} catch (error) {
|
|
431
|
+
if (isApiError(error)) throw error;
|
|
432
|
+
throw new ApiError(`${method} request failed with status ${response.status}`, response.status, null);
|
|
433
|
+
}
|
|
434
|
+
if (response.status === 204 || response.headers.get("content-length") === "0") return null;
|
|
435
|
+
try {
|
|
436
|
+
const data = await response.json();
|
|
437
|
+
if (data === null || data === void 0) throw new ApiError("Unexpected null/undefined in JSON response", response.status, null);
|
|
438
|
+
return data;
|
|
439
|
+
} catch (parseError) {
|
|
440
|
+
if (isApiError(parseError)) throw parseError;
|
|
441
|
+
throw new ApiError("Failed to parse response as JSON", response.status, null);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Request function for endpoints that may return null (204 No Content).
|
|
446
|
+
* Properly types the return as T | null.
|
|
447
|
+
*/
|
|
448
|
+
async function requestNullable(endpoint, options = {}) {
|
|
449
|
+
return request(endpoint, options);
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Safe request wrapper that returns a discriminated union instead of throwing.
|
|
453
|
+
* Use `isApiSuccess` or `isApiFailure` to narrow the result.
|
|
454
|
+
*/
|
|
455
|
+
async function safeRequest(endpoint, options = {}) {
|
|
456
|
+
try {
|
|
457
|
+
return {
|
|
458
|
+
success: true,
|
|
459
|
+
data: await request(endpoint, options)
|
|
460
|
+
};
|
|
461
|
+
} catch (error) {
|
|
462
|
+
if (isApiError(error)) return {
|
|
463
|
+
success: false,
|
|
464
|
+
error
|
|
465
|
+
};
|
|
466
|
+
return {
|
|
467
|
+
success: false,
|
|
468
|
+
error: new ApiError(error instanceof Error ? error.message : "Unknown error", 0, null)
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Helper to safely convert typed params to Record<string, unknown>.
|
|
474
|
+
* Type assertion required: TypeScript's structural typing allows any object
|
|
475
|
+
* to be treated as Record<string, unknown> when we only need to iterate
|
|
476
|
+
* over its entries. This is safe because buildUrl only reads properties.
|
|
477
|
+
*/
|
|
478
|
+
function toParams(params) {
|
|
479
|
+
return params;
|
|
480
|
+
}
|
|
481
|
+
const get = (endpoint, params, options) => {
|
|
482
|
+
const baseOptions = {
|
|
483
|
+
...options,
|
|
484
|
+
method: HTTP_METHODS.GET
|
|
485
|
+
};
|
|
486
|
+
const convertedParams = toParams(params);
|
|
487
|
+
return request(endpoint, convertedParams !== void 0 ? {
|
|
488
|
+
...baseOptions,
|
|
489
|
+
params: convertedParams
|
|
490
|
+
} : baseOptions);
|
|
491
|
+
};
|
|
492
|
+
const post = (endpoint, body, options) => request(endpoint, {
|
|
493
|
+
...options,
|
|
494
|
+
method: HTTP_METHODS.POST,
|
|
495
|
+
body
|
|
496
|
+
});
|
|
497
|
+
const put = (endpoint, body, options) => request(endpoint, {
|
|
498
|
+
...options,
|
|
499
|
+
method: HTTP_METHODS.PUT,
|
|
500
|
+
body
|
|
501
|
+
});
|
|
502
|
+
const patch = (endpoint, body, options) => request(endpoint, {
|
|
503
|
+
...options,
|
|
504
|
+
method: HTTP_METHODS.PATCH,
|
|
505
|
+
body
|
|
506
|
+
});
|
|
507
|
+
const del = (endpoint, options) => request(endpoint, {
|
|
508
|
+
...options,
|
|
509
|
+
method: HTTP_METHODS.DELETE
|
|
510
|
+
});
|
|
511
|
+
return {
|
|
512
|
+
fetchClient,
|
|
513
|
+
request,
|
|
514
|
+
requestNullable,
|
|
515
|
+
safeRequest,
|
|
516
|
+
get,
|
|
517
|
+
post,
|
|
518
|
+
put,
|
|
519
|
+
patch,
|
|
520
|
+
delete: del,
|
|
521
|
+
products: {
|
|
522
|
+
list: (params) => get("/company/v1/products", params),
|
|
523
|
+
get: (id) => get(`/company/v1/products/${id}`),
|
|
524
|
+
search: (query, params) => get("/company/v1/products", {
|
|
525
|
+
search_query: query,
|
|
526
|
+
...params
|
|
527
|
+
})
|
|
528
|
+
},
|
|
529
|
+
orders: {
|
|
530
|
+
list: (params) => get("/orders", params),
|
|
531
|
+
get: (id) => get(`/orders/${id}`),
|
|
532
|
+
create: (data) => post("/orders", data)
|
|
533
|
+
},
|
|
534
|
+
users: { me: () => get("/api/me") },
|
|
535
|
+
reps: {
|
|
536
|
+
current: () => get("/reps/me"),
|
|
537
|
+
updateProfile: (data) => patch("/reps/me", data)
|
|
538
|
+
},
|
|
539
|
+
profile: { get: () => get("/rep_app/manifest") },
|
|
540
|
+
app: {
|
|
541
|
+
getRaw: async () => {
|
|
542
|
+
return await getFluidOSManifest(fetchClient, { platform: "browser" });
|
|
543
|
+
},
|
|
544
|
+
get: async () => {
|
|
545
|
+
return transformManifestToRepAppData(await getFluidOSManifest(fetchClient, { platform: "browser" }));
|
|
546
|
+
}
|
|
547
|
+
},
|
|
548
|
+
permissions: { get: () => get("/company/roles/my_permissions") },
|
|
549
|
+
analytics: {
|
|
550
|
+
dashboard: () => get("/analytics/dashboard"),
|
|
551
|
+
sales: (params) => get("/analytics/sales", params)
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
//#endregion
|
|
556
|
+
//#region src/themes/index.ts
|
|
557
|
+
var themes_exports = /* @__PURE__ */ __exportAll({});
|
|
558
|
+
import * as import__fluid_app_portal_core_theme from "@fluid-app/portal-core/theme";
|
|
559
|
+
__reExport(themes_exports, import__fluid_app_portal_core_theme);
|
|
560
|
+
//#endregion
|
|
561
|
+
//#region src/providers/FluidThemeProvider.tsx
|
|
562
|
+
/**
|
|
563
|
+
* Theme Provider for Fluid SDK
|
|
564
|
+
* Handles CSS variable injection using portal-core's theme engine.
|
|
565
|
+
* Accepts ThemeDefinition objects and resolves them into CSS via generateThemeCSS.
|
|
566
|
+
*/
|
|
567
|
+
const ThemeContext = createContext(null);
|
|
568
|
+
/**
|
|
569
|
+
* Apply a theme to the DOM using the shared portal-core pipeline.
|
|
570
|
+
* Also sets data attributes on the container for CSS selector targeting.
|
|
571
|
+
*/
|
|
572
|
+
function applyThemeToDOM(theme, mode, container) {
|
|
573
|
+
const target = container ?? document.documentElement;
|
|
574
|
+
(0, themes_exports.applyTheme)((0, themes_exports.resolveTheme)(theme));
|
|
575
|
+
target.dataset.theme = theme.id;
|
|
576
|
+
if (mode) target.dataset.themeMode = mode;
|
|
577
|
+
else delete target.dataset.themeMode;
|
|
578
|
+
}
|
|
579
|
+
function FluidThemeProvider({ children, initialTheme, container }) {
|
|
580
|
+
const [currentTheme, setCurrentTheme] = useState(initialTheme ?? null);
|
|
581
|
+
const [mode, setMode] = useState(void 0);
|
|
582
|
+
useEffect(() => {
|
|
583
|
+
if (currentTheme) applyThemeToDOM(currentTheme, mode, container ?? null);
|
|
584
|
+
return () => {
|
|
585
|
+
(0, themes_exports.removeAllThemes)();
|
|
586
|
+
};
|
|
587
|
+
}, [
|
|
588
|
+
currentTheme,
|
|
589
|
+
mode,
|
|
590
|
+
container
|
|
591
|
+
]);
|
|
592
|
+
const setTheme = useCallback((theme) => {
|
|
593
|
+
setCurrentTheme(theme);
|
|
594
|
+
}, []);
|
|
595
|
+
const setThemeMode = useCallback((newMode) => {
|
|
596
|
+
setMode(newMode);
|
|
597
|
+
}, []);
|
|
598
|
+
const value = useMemo(() => ({
|
|
599
|
+
currentTheme,
|
|
600
|
+
setTheme,
|
|
601
|
+
setThemeMode,
|
|
602
|
+
mode
|
|
603
|
+
}), [
|
|
604
|
+
currentTheme,
|
|
605
|
+
setTheme,
|
|
606
|
+
setThemeMode,
|
|
607
|
+
mode
|
|
608
|
+
]);
|
|
609
|
+
return /* @__PURE__ */ jsx(ThemeContext.Provider, {
|
|
610
|
+
value,
|
|
611
|
+
children
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Hook to access theme context
|
|
616
|
+
* Must be used within a FluidThemeProvider
|
|
617
|
+
*/
|
|
618
|
+
function useThemeContext() {
|
|
619
|
+
const context = useContext(ThemeContext);
|
|
620
|
+
if (!context) throw new Error("useThemeContext must be used within a FluidThemeProvider");
|
|
621
|
+
return context;
|
|
622
|
+
}
|
|
623
|
+
//#endregion
|
|
624
|
+
//#region src/providers/FluidAuthProvider.tsx
|
|
625
|
+
/**
|
|
626
|
+
* FluidAuthProvider - Authentication Provider for Fluid Portal SDK
|
|
627
|
+
*
|
|
628
|
+
* Handles JWT token extraction from URL, validation, storage, and
|
|
629
|
+
* provides authentication context to child components.
|
|
630
|
+
*/
|
|
631
|
+
/**
|
|
632
|
+
* Auth context - null when outside provider
|
|
633
|
+
*/
|
|
634
|
+
const FluidAuthContext = createContext(null);
|
|
635
|
+
function authReducer(state, action) {
|
|
636
|
+
switch (action.type) {
|
|
637
|
+
case "SET_AUTH": return {
|
|
638
|
+
isLoading: false,
|
|
639
|
+
token: action.token,
|
|
640
|
+
user: action.user,
|
|
641
|
+
error: action.error
|
|
642
|
+
};
|
|
643
|
+
case "CLEAR_AUTH": return {
|
|
644
|
+
...state,
|
|
645
|
+
token: null,
|
|
646
|
+
user: null,
|
|
647
|
+
error: null
|
|
648
|
+
};
|
|
649
|
+
case "DONE_LOADING": return {
|
|
650
|
+
...state,
|
|
651
|
+
isLoading: false
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
const initialAuthState = {
|
|
656
|
+
isLoading: true,
|
|
657
|
+
token: null,
|
|
658
|
+
user: null,
|
|
659
|
+
error: null
|
|
660
|
+
};
|
|
661
|
+
/**
|
|
662
|
+
* Authentication provider for Fluid portal applications.
|
|
663
|
+
*
|
|
664
|
+
* On mount, this provider:
|
|
665
|
+
* 1. Checks for a token in the URL (passed from parent app)
|
|
666
|
+
* 2. Cleans token from URL immediately (security)
|
|
667
|
+
* 3. Falls back to stored token (cookie/localStorage)
|
|
668
|
+
* 4. Validates the token (checks expiration)
|
|
669
|
+
* 5. Stores valid tokens for future use
|
|
670
|
+
* 6. Calls onAuthFailure if no valid token found
|
|
671
|
+
*
|
|
672
|
+
* @example
|
|
673
|
+
* ```tsx
|
|
674
|
+
* import { FluidAuthProvider } from "@fluid-app/portal-sdk";
|
|
675
|
+
*
|
|
676
|
+
* function App() {
|
|
677
|
+
* return (
|
|
678
|
+
* <FluidAuthProvider
|
|
679
|
+
* config={{
|
|
680
|
+
* onAuthFailure: () => {
|
|
681
|
+
* window.location.href = "/login";
|
|
682
|
+
* },
|
|
683
|
+
* }}
|
|
684
|
+
* >
|
|
685
|
+
* <YourApp />
|
|
686
|
+
* </FluidAuthProvider>
|
|
687
|
+
* );
|
|
688
|
+
* }
|
|
689
|
+
* ```
|
|
690
|
+
*/
|
|
691
|
+
function FluidAuthProvider({ children, config }) {
|
|
692
|
+
const configRef = useRef(config);
|
|
693
|
+
configRef.current = config;
|
|
694
|
+
const [state, dispatch] = useReducer(authReducer, initialAuthState);
|
|
695
|
+
const { isLoading, token, user, error } = state;
|
|
696
|
+
useEffect(() => {
|
|
697
|
+
const initializeAuth = async () => {
|
|
698
|
+
const handleAuthFailure = () => {
|
|
699
|
+
const current = configRef.current;
|
|
700
|
+
resolveAuthFailureHandler(current?.onAuthFailure, current?.authUrl)();
|
|
701
|
+
};
|
|
702
|
+
try {
|
|
703
|
+
if (isDevBypassActive(config?.devBypass)) {
|
|
704
|
+
const envToken = import.meta.env.VITE_DEV_TOKEN;
|
|
705
|
+
if (envToken) {
|
|
706
|
+
const validation = validateToken(envToken, config?.gracePeriodMs);
|
|
707
|
+
if (validation.isValid && validation.payload) {
|
|
708
|
+
storeToken(envToken, config);
|
|
709
|
+
dispatch({
|
|
710
|
+
type: "SET_AUTH",
|
|
711
|
+
token: envToken,
|
|
712
|
+
user: validation.payload,
|
|
713
|
+
error: null
|
|
714
|
+
});
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
console.warn("[FluidAuth] VITE_DEV_TOKEN is invalid or expired, falling back to mock user");
|
|
718
|
+
}
|
|
719
|
+
console.warn("[FluidAuth] Dev bypass active - using mock user. API calls will fail without a real token.");
|
|
720
|
+
dispatch({
|
|
721
|
+
type: "SET_AUTH",
|
|
722
|
+
token: null,
|
|
723
|
+
user: createDevUser(),
|
|
724
|
+
error: null
|
|
725
|
+
});
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
const tokenKey = config?.tokenKey ?? "fluidUserToken";
|
|
729
|
+
let candidateToken = extractTokenFromUrl(tokenKey);
|
|
730
|
+
if (!candidateToken && tokenKey !== AUTH_REDIRECT_TOKEN_KEY) candidateToken = extractTokenFromUrl(AUTH_REDIRECT_TOKEN_KEY);
|
|
731
|
+
cleanTokenFromUrl(tokenKey);
|
|
732
|
+
cleanTokenFromUrl(AUTH_REDIRECT_TOKEN_KEY);
|
|
733
|
+
if (!candidateToken) candidateToken = getStoredToken(config);
|
|
734
|
+
if (candidateToken) {
|
|
735
|
+
let payload = null;
|
|
736
|
+
if (config?.jwksUrl) {
|
|
737
|
+
payload = await verifyToken(candidateToken, config.jwksUrl);
|
|
738
|
+
if (!payload) {
|
|
739
|
+
clearTokens(config);
|
|
740
|
+
dispatch({
|
|
741
|
+
type: "SET_AUTH",
|
|
742
|
+
token: null,
|
|
743
|
+
user: null,
|
|
744
|
+
error: /* @__PURE__ */ new Error("JWT signature verification failed")
|
|
745
|
+
});
|
|
746
|
+
handleAuthFailure();
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
if (isTokenExpired(candidateToken, config?.gracePeriodMs)) {
|
|
750
|
+
clearTokens(config);
|
|
751
|
+
dispatch({
|
|
752
|
+
type: "SET_AUTH",
|
|
753
|
+
token: null,
|
|
754
|
+
user: null,
|
|
755
|
+
error: /* @__PURE__ */ new Error("Token has expired")
|
|
756
|
+
});
|
|
757
|
+
handleAuthFailure();
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
} else {
|
|
761
|
+
const validation = validateToken(candidateToken, config?.gracePeriodMs);
|
|
762
|
+
if (validation.isValid && validation.payload) payload = validation.payload;
|
|
763
|
+
else {
|
|
764
|
+
clearTokens(config);
|
|
765
|
+
dispatch({
|
|
766
|
+
type: "SET_AUTH",
|
|
767
|
+
token: null,
|
|
768
|
+
user: null,
|
|
769
|
+
error: new Error(validation.error ?? "Invalid token")
|
|
770
|
+
});
|
|
771
|
+
handleAuthFailure();
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
storeToken(candidateToken, config);
|
|
776
|
+
dispatch({
|
|
777
|
+
type: "SET_AUTH",
|
|
778
|
+
token: candidateToken,
|
|
779
|
+
user: payload,
|
|
780
|
+
error: null
|
|
781
|
+
});
|
|
782
|
+
} else {
|
|
783
|
+
dispatch({
|
|
784
|
+
type: "SET_AUTH",
|
|
785
|
+
token: null,
|
|
786
|
+
user: null,
|
|
787
|
+
error: /* @__PURE__ */ new Error("No authentication token found")
|
|
788
|
+
});
|
|
789
|
+
handleAuthFailure();
|
|
790
|
+
}
|
|
791
|
+
} catch (err) {
|
|
792
|
+
dispatch({
|
|
793
|
+
type: "SET_AUTH",
|
|
794
|
+
token: null,
|
|
795
|
+
user: null,
|
|
796
|
+
error: err instanceof Error ? err : /* @__PURE__ */ new Error("Authentication error")
|
|
797
|
+
});
|
|
798
|
+
handleAuthFailure();
|
|
799
|
+
} finally {
|
|
800
|
+
dispatch({ type: "DONE_LOADING" });
|
|
801
|
+
}
|
|
802
|
+
};
|
|
803
|
+
initializeAuth();
|
|
804
|
+
}, []);
|
|
805
|
+
const clearAuth = useCallback(() => {
|
|
806
|
+
clearTokens(configRef.current);
|
|
807
|
+
dispatch({ type: "CLEAR_AUTH" });
|
|
808
|
+
}, []);
|
|
809
|
+
const contextValue = useMemo(() => ({
|
|
810
|
+
isAuthenticated: user !== null,
|
|
811
|
+
isLoading,
|
|
812
|
+
user,
|
|
813
|
+
token,
|
|
814
|
+
clearAuth,
|
|
815
|
+
error
|
|
816
|
+
}), [
|
|
817
|
+
token,
|
|
818
|
+
isLoading,
|
|
819
|
+
user,
|
|
820
|
+
clearAuth,
|
|
821
|
+
error
|
|
822
|
+
]);
|
|
823
|
+
return /* @__PURE__ */ jsx(FluidAuthContext.Provider, {
|
|
824
|
+
value: contextValue,
|
|
825
|
+
children
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* Hook to access the auth context directly.
|
|
830
|
+
* Prefer using `useFluidAuth` for most use cases.
|
|
831
|
+
*
|
|
832
|
+
* @throws Error if used outside FluidAuthProvider
|
|
833
|
+
*/
|
|
834
|
+
function useFluidAuthContext() {
|
|
835
|
+
const context = useContext(FluidAuthContext);
|
|
836
|
+
if (!context) throw new Error("useFluidAuthContext must be used within a FluidAuthProvider. Wrap your app with <FluidAuthProvider> to use authentication features.");
|
|
837
|
+
return context;
|
|
838
|
+
}
|
|
839
|
+
/** Safe accessor — returns null when outside FluidAuthProvider (no throw) */
|
|
840
|
+
function useFluidAuthOptional() {
|
|
841
|
+
return useContext(FluidAuthContext);
|
|
842
|
+
}
|
|
843
|
+
//#endregion
|
|
844
|
+
//#region src/core/default-widget-registry.ts
|
|
845
|
+
const DEFAULT_SDK_WIDGET_REGISTRY = createWidgetRegistry({
|
|
846
|
+
AlertWidget,
|
|
847
|
+
CalendarWidget,
|
|
848
|
+
CarouselWidget,
|
|
849
|
+
CatchUpWidget,
|
|
850
|
+
ChartWidget,
|
|
851
|
+
ContainerWidget,
|
|
852
|
+
EmbedWidget,
|
|
853
|
+
ImageWidget,
|
|
854
|
+
LayoutWidget,
|
|
855
|
+
ListWidget,
|
|
856
|
+
MySiteWidget,
|
|
857
|
+
NestedWidget,
|
|
858
|
+
QuickShareWidget,
|
|
859
|
+
RecentActivityWidget,
|
|
860
|
+
SpacerWidget,
|
|
861
|
+
TableWidget,
|
|
862
|
+
TextWidget,
|
|
863
|
+
ToDoWidget,
|
|
864
|
+
VideoWidget
|
|
865
|
+
});
|
|
866
|
+
//#endregion
|
|
867
|
+
//#region src/providers/FluidProvider.tsx
|
|
868
|
+
/**
|
|
869
|
+
* Main Fluid SDK Provider
|
|
870
|
+
* Wraps QueryClientProvider and FluidThemeProvider
|
|
871
|
+
*/
|
|
872
|
+
const FluidContext = createContext(null);
|
|
873
|
+
/**
|
|
874
|
+
* Main provider for the Fluid Portal SDK
|
|
875
|
+
*
|
|
876
|
+
* @example
|
|
877
|
+
* ```tsx
|
|
878
|
+
* import { FluidProvider } from "@fluid-app/portal-sdk";
|
|
879
|
+
*
|
|
880
|
+
* function App() {
|
|
881
|
+
* return (
|
|
882
|
+
* <FluidProvider
|
|
883
|
+
* config={{
|
|
884
|
+
* baseUrl: "https://api.fluid.app/api",
|
|
885
|
+
* getAuthToken: () => localStorage.getItem("token"),
|
|
886
|
+
* }}
|
|
887
|
+
* >
|
|
888
|
+
* <YourApp />
|
|
889
|
+
* </FluidProvider>
|
|
890
|
+
* );
|
|
891
|
+
* }
|
|
892
|
+
* ```
|
|
893
|
+
*/
|
|
894
|
+
function FluidProvider({ config, children, queryClient, initialTheme, themeContainer, widgetRegistry, variables }) {
|
|
895
|
+
const defaultQueryClient = useMemo(() => new QueryClient({ defaultOptions: { queries: {
|
|
896
|
+
staleTime: 1e3 * 60,
|
|
897
|
+
retry: 1
|
|
898
|
+
} } }), []);
|
|
899
|
+
const configRef = useRef(config);
|
|
900
|
+
configRef.current = config;
|
|
901
|
+
const client = useMemo(() => createFluidClient({
|
|
902
|
+
...configRef.current,
|
|
903
|
+
getAuthToken: () => configRef.current.getAuthToken?.() ?? null,
|
|
904
|
+
onAuthError: () => configRef.current.onAuthError?.()
|
|
905
|
+
}), [config.baseUrl]);
|
|
906
|
+
const contextValue = useMemo(() => ({
|
|
907
|
+
client,
|
|
908
|
+
config: configRef.current
|
|
909
|
+
}), [client]);
|
|
910
|
+
const getApiHeaders = useCallback(() => {
|
|
911
|
+
const headers = { "Content-Type": "application/json" };
|
|
912
|
+
const getAuthToken = configRef.current.getAuthToken;
|
|
913
|
+
if (typeof getAuthToken === "function") {
|
|
914
|
+
const tokenOrPromise = getAuthToken();
|
|
915
|
+
if (typeof tokenOrPromise === "string") headers.Authorization = `Bearer ${tokenOrPromise}`;
|
|
916
|
+
}
|
|
917
|
+
return headers;
|
|
918
|
+
}, []);
|
|
919
|
+
const dataSourceBaseUrl = useMemo(() => {
|
|
920
|
+
const base = config.baseUrl.replace(/\/+$/, "");
|
|
921
|
+
return base.endsWith("/api") ? base : `${base}/api`;
|
|
922
|
+
}, [config.baseUrl]);
|
|
923
|
+
const authContext = useFluidAuthOptional();
|
|
924
|
+
const autoVariables = useMemo(() => {
|
|
925
|
+
if (authContext?.user?.id != null) return { rep_id: String(authContext.user.id) };
|
|
926
|
+
}, [authContext?.user?.id]);
|
|
927
|
+
const effectiveVariables = variables ?? autoVariables;
|
|
928
|
+
const themeProviderProps = {
|
|
929
|
+
...initialTheme !== void 0 && { initialTheme },
|
|
930
|
+
...themeContainer !== void 0 && { container: themeContainer }
|
|
931
|
+
};
|
|
932
|
+
const registry = widgetRegistry ?? DEFAULT_SDK_WIDGET_REGISTRY;
|
|
933
|
+
return /* @__PURE__ */ jsx(QueryClientProvider, {
|
|
934
|
+
client: queryClient ?? defaultQueryClient,
|
|
935
|
+
children: /* @__PURE__ */ jsx(FluidContext.Provider, {
|
|
936
|
+
value: contextValue,
|
|
937
|
+
children: /* @__PURE__ */ jsx(DataSourceRegistryProvider, {
|
|
938
|
+
baseUrl: dataSourceBaseUrl,
|
|
939
|
+
getApiHeaders,
|
|
940
|
+
variables: effectiveVariables,
|
|
941
|
+
children: /* @__PURE__ */ jsx(RegistryProvider, {
|
|
942
|
+
registry,
|
|
943
|
+
children: /* @__PURE__ */ jsx(FluidThemeProvider, {
|
|
944
|
+
...themeProviderProps,
|
|
945
|
+
children
|
|
946
|
+
})
|
|
947
|
+
})
|
|
948
|
+
})
|
|
949
|
+
})
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Hook to access the Fluid context
|
|
954
|
+
* Must be used within a FluidProvider
|
|
955
|
+
*/
|
|
956
|
+
function useFluidContext() {
|
|
957
|
+
const context = useContext(FluidContext);
|
|
958
|
+
if (!context) throw new Error("useFluidContext must be used within a FluidProvider");
|
|
959
|
+
return context;
|
|
960
|
+
}
|
|
961
|
+
//#endregion
|
|
962
|
+
//#region src/hooks/use-fluid-api.ts
|
|
963
|
+
/**
|
|
964
|
+
* Hook to access the Fluid API client
|
|
965
|
+
*
|
|
966
|
+
* @example
|
|
967
|
+
* ```tsx
|
|
968
|
+
* function ProductList() {
|
|
969
|
+
* const api = useFluidApi();
|
|
970
|
+
*
|
|
971
|
+
* const { data: products } = useQuery({
|
|
972
|
+
* queryKey: ["products"],
|
|
973
|
+
* queryFn: () => api.products.list(),
|
|
974
|
+
* });
|
|
975
|
+
*
|
|
976
|
+
* return <ul>{products?.map(p => <li key={p.id}>{p.name}</li>)}</ul>;
|
|
977
|
+
* }
|
|
978
|
+
* ```
|
|
979
|
+
*/
|
|
980
|
+
function useFluidApi() {
|
|
981
|
+
const { client } = useFluidContext();
|
|
982
|
+
return client;
|
|
983
|
+
}
|
|
984
|
+
//#endregion
|
|
985
|
+
//#region src/messaging/use-messaging-auth.ts
|
|
986
|
+
/**
|
|
987
|
+
* Bridge hook: maps portal SDK auth context to MessagingAuthContext.
|
|
988
|
+
*
|
|
989
|
+
* The messaging system identifies users by `recipient_id`, which is NOT in the
|
|
990
|
+
* JWT payload or the /reps/me endpoint. It IS returned by GET /api/me.
|
|
991
|
+
* This hook fetches that data and maps it into the shape MessagingApp expects.
|
|
992
|
+
*/
|
|
993
|
+
const USERS_ME_QUERY_KEY = [
|
|
994
|
+
"fluid",
|
|
995
|
+
"users",
|
|
996
|
+
"me"
|
|
997
|
+
];
|
|
998
|
+
function useMessagingAuth() {
|
|
999
|
+
const auth = useFluidAuthContext();
|
|
1000
|
+
const api = useFluidApi();
|
|
1001
|
+
const { data: userMe, isLoading: isUserMeLoading, isError } = useQuery({
|
|
1002
|
+
queryKey: USERS_ME_QUERY_KEY,
|
|
1003
|
+
queryFn: () => api.users.me(),
|
|
1004
|
+
enabled: auth.isAuthenticated,
|
|
1005
|
+
staleTime: Infinity,
|
|
1006
|
+
retry: 1
|
|
1007
|
+
});
|
|
1008
|
+
if (auth.isLoading || auth.isAuthenticated && isUserMeLoading) return {
|
|
1009
|
+
recipientId: null,
|
|
1010
|
+
companyId: null,
|
|
1011
|
+
currentUser: null,
|
|
1012
|
+
isLoading: true
|
|
1013
|
+
};
|
|
1014
|
+
if (!auth.isAuthenticated || isError || !userMe) return {
|
|
1015
|
+
recipientId: null,
|
|
1016
|
+
companyId: null,
|
|
1017
|
+
currentUser: null,
|
|
1018
|
+
isLoading: false
|
|
1019
|
+
};
|
|
1020
|
+
const currentUser = userMe.recipient_id ? {
|
|
1021
|
+
id: userMe.id,
|
|
1022
|
+
recipientId: userMe.recipient_id,
|
|
1023
|
+
firstName: userMe.first_name ?? "",
|
|
1024
|
+
lastName: userMe.last_name ?? "",
|
|
1025
|
+
email: userMe.email,
|
|
1026
|
+
...userMe.image_url != null && { imageUrl: userMe.image_url },
|
|
1027
|
+
...userMe.affiliate_id != null && { affiliateId: userMe.affiliate_id }
|
|
1028
|
+
} : null;
|
|
1029
|
+
return {
|
|
1030
|
+
recipientId: userMe.recipient_id,
|
|
1031
|
+
companyId: userMe.company_id ?? null,
|
|
1032
|
+
currentUser,
|
|
1033
|
+
isLoading: false
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
//#endregion
|
|
1037
|
+
//#region src/messaging/use-messaging-config.ts
|
|
1038
|
+
/**
|
|
1039
|
+
* Hook that derives MessagingApiConfig from the portal SDK's FluidProvider context.
|
|
1040
|
+
*
|
|
1041
|
+
* Maps FluidSDKConfig fields to the shape expected by MessagingApp:
|
|
1042
|
+
* - baseUrl -> from config.baseUrl
|
|
1043
|
+
* - getHeaders -> builds Authorization header from config.getAuthToken()
|
|
1044
|
+
* - onAuthError -> from config.onAuthError
|
|
1045
|
+
* - websocketUrl -> config.websocketUrl or derived from baseUrl
|
|
1046
|
+
* - token -> from auth context
|
|
1047
|
+
*/
|
|
1048
|
+
function deriveWebsocketUrl(baseUrl) {
|
|
1049
|
+
return `${baseUrl.replace(/\/+$/, "").replace(/\/api$/, "")}/cable`;
|
|
1050
|
+
}
|
|
1051
|
+
function useMessagingConfig() {
|
|
1052
|
+
const { config } = useFluidContext();
|
|
1053
|
+
const auth = useFluidAuthContext();
|
|
1054
|
+
const getHeaders = useCallback(async () => {
|
|
1055
|
+
const headers = {
|
|
1056
|
+
"Content-Type": "application/json",
|
|
1057
|
+
...config.defaultHeaders
|
|
1058
|
+
};
|
|
1059
|
+
if (config.getAuthToken) {
|
|
1060
|
+
const token = await config.getAuthToken();
|
|
1061
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
1062
|
+
}
|
|
1063
|
+
return headers;
|
|
1064
|
+
}, [config]);
|
|
1065
|
+
const apiBaseUrl = useMemo(() => {
|
|
1066
|
+
const base = config.baseUrl.replace(/\/+$/, "");
|
|
1067
|
+
return base.endsWith("/api") ? base : `${base}/api`;
|
|
1068
|
+
}, [config.baseUrl]);
|
|
1069
|
+
return {
|
|
1070
|
+
apiConfig: useMemo(() => ({
|
|
1071
|
+
baseUrl: apiBaseUrl,
|
|
1072
|
+
getHeaders,
|
|
1073
|
+
...config.onAuthError != null && { onAuthError: config.onAuthError }
|
|
1074
|
+
}), [
|
|
1075
|
+
apiBaseUrl,
|
|
1076
|
+
config.onAuthError,
|
|
1077
|
+
getHeaders
|
|
1078
|
+
]),
|
|
1079
|
+
websocketUrl: useMemo(() => config.websocketUrl ?? deriveWebsocketUrl(config.baseUrl), [config.websocketUrl, config.baseUrl]),
|
|
1080
|
+
token: auth.token
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
//#endregion
|
|
1084
|
+
//#region src/messaging/fluid-file-uploader.ts
|
|
1085
|
+
const FILESTACK_UPLOAD_URL = "https://www.filestackapi.com/api/store/S3";
|
|
1086
|
+
function getImageDimensions(file) {
|
|
1087
|
+
if (!file.type.startsWith("image/")) return Promise.resolve(null);
|
|
1088
|
+
return new Promise((resolve) => {
|
|
1089
|
+
const img = new Image();
|
|
1090
|
+
const url = URL.createObjectURL(file);
|
|
1091
|
+
img.onload = () => {
|
|
1092
|
+
URL.revokeObjectURL(url);
|
|
1093
|
+
resolve({
|
|
1094
|
+
width: img.naturalWidth,
|
|
1095
|
+
height: img.naturalHeight
|
|
1096
|
+
});
|
|
1097
|
+
};
|
|
1098
|
+
img.onerror = () => {
|
|
1099
|
+
URL.revokeObjectURL(url);
|
|
1100
|
+
resolve(null);
|
|
1101
|
+
};
|
|
1102
|
+
img.src = url;
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
function uploadToFilestack(file, apiKey, callbacks) {
|
|
1106
|
+
const xhr = new XMLHttpRequest();
|
|
1107
|
+
let aborted = false;
|
|
1108
|
+
getImageDimensions(file).then((metadata) => {
|
|
1109
|
+
if (aborted) return;
|
|
1110
|
+
const url = `${FILESTACK_UPLOAD_URL}?key=${encodeURIComponent(apiKey)}`;
|
|
1111
|
+
xhr.open("POST", url);
|
|
1112
|
+
xhr.setRequestHeader("Content-Type", file.type);
|
|
1113
|
+
xhr.upload.onprogress = (event) => {
|
|
1114
|
+
if (event.lengthComputable) {
|
|
1115
|
+
const progress = Math.round(event.loaded / event.total * 100);
|
|
1116
|
+
callbacks.onProgress(progress);
|
|
1117
|
+
}
|
|
1118
|
+
};
|
|
1119
|
+
xhr.onload = () => {
|
|
1120
|
+
if (xhr.status >= 200 && xhr.status < 300) try {
|
|
1121
|
+
const result = {
|
|
1122
|
+
url: JSON.parse(xhr.responseText).url,
|
|
1123
|
+
size: file.size,
|
|
1124
|
+
mimetype: file.type,
|
|
1125
|
+
kind: getFileTypeFromMimetype(file.type),
|
|
1126
|
+
metadata
|
|
1127
|
+
};
|
|
1128
|
+
callbacks.onSuccess(result);
|
|
1129
|
+
} catch {
|
|
1130
|
+
callbacks.onError(/* @__PURE__ */ new Error("Failed to parse upload response"));
|
|
1131
|
+
}
|
|
1132
|
+
else callbacks.onError(/* @__PURE__ */ new Error(`Upload failed with status ${xhr.status}`));
|
|
1133
|
+
};
|
|
1134
|
+
xhr.onerror = () => {
|
|
1135
|
+
if (!aborted) callbacks.onError(/* @__PURE__ */ new Error("Upload failed due to a network error"));
|
|
1136
|
+
};
|
|
1137
|
+
xhr.onabort = () => {};
|
|
1138
|
+
xhr.send(file);
|
|
1139
|
+
}).catch((error) => {
|
|
1140
|
+
if (!aborted) callbacks.onError(error instanceof Error ? error : /* @__PURE__ */ new Error("Upload preparation failed"));
|
|
1141
|
+
});
|
|
1142
|
+
return { abort: () => {
|
|
1143
|
+
aborted = true;
|
|
1144
|
+
xhr.abort();
|
|
1145
|
+
} };
|
|
1146
|
+
}
|
|
1147
|
+
/**
|
|
1148
|
+
* Creates a FileUploader that uploads to Filestack using the REST API.
|
|
1149
|
+
*
|
|
1150
|
+
* @param apiKey - Filestack API key. If falsy, returns a noop uploader
|
|
1151
|
+
* that rejects uploads with a helpful error message.
|
|
1152
|
+
*/
|
|
1153
|
+
function createFluidFileUploader(apiKey) {
|
|
1154
|
+
if (!apiKey) return { uploadFile: (_file, callbacks) => {
|
|
1155
|
+
callbacks.onError(/* @__PURE__ */ new Error("File uploads are not configured. Set filestackApiKey in your SDK config to enable attachments."));
|
|
1156
|
+
return { abort: () => {} };
|
|
1157
|
+
} };
|
|
1158
|
+
return { uploadFile: (file, callbacks) => uploadToFilestack(file, apiKey, callbacks) };
|
|
1159
|
+
}
|
|
1160
|
+
//#endregion
|
|
1161
|
+
//#region src/screens/MessagingScreen.tsx
|
|
1162
|
+
var MessagingScreen_exports = /* @__PURE__ */ __exportAll({
|
|
1163
|
+
MessagingScreen: () => MessagingScreen,
|
|
1164
|
+
messagingScreenPropertySchema: () => messagingScreenPropertySchema
|
|
1165
|
+
});
|
|
1166
|
+
function renderImage(props) {
|
|
1167
|
+
return /* @__PURE__ */ jsx("img", {
|
|
1168
|
+
src: props.src,
|
|
1169
|
+
alt: props.alt,
|
|
1170
|
+
width: props.width,
|
|
1171
|
+
height: props.height,
|
|
1172
|
+
className: props.className
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
function defaultToast(message, type) {
|
|
1176
|
+
if (type === "error") console.warn("[Messaging]", message);
|
|
1177
|
+
}
|
|
1178
|
+
function MessagingScreen({ onToast, filestackApiKey, websocketUrl: websocketUrlOverride, background, textColor, accentColor, padding, borderRadius, ...divProps }) {
|
|
1179
|
+
const { config } = useFluidContext();
|
|
1180
|
+
const { apiConfig, websocketUrl, token } = useMessagingConfig();
|
|
1181
|
+
const messagingAuth = useMessagingAuth();
|
|
1182
|
+
const effectiveApiKey = filestackApiKey ?? config.filestackApiKey;
|
|
1183
|
+
const uploader = useMemo(() => createFluidFileUploader(effectiveApiKey), [effectiveApiKey]);
|
|
1184
|
+
const effectiveWsUrl = websocketUrlOverride ?? websocketUrl;
|
|
1185
|
+
const effectiveToast = onToast ?? defaultToast;
|
|
1186
|
+
const searchUsers = useCallback(async (query) => {
|
|
1187
|
+
return ((await listConnectedRecipients(apiConfig, {
|
|
1188
|
+
filterrific: { search_query: query },
|
|
1189
|
+
per_page: 10,
|
|
1190
|
+
page: 1
|
|
1191
|
+
}))?.[1]?.items ?? []).map((user) => {
|
|
1192
|
+
const name = [user.first_name, user.last_name].filter(Boolean).length > 0 ? [user.first_name, user.last_name].filter(Boolean).join(" ") : `User ${user.id}`;
|
|
1193
|
+
return {
|
|
1194
|
+
id: String(user.id),
|
|
1195
|
+
label: name,
|
|
1196
|
+
imageType: "user",
|
|
1197
|
+
userData: {
|
|
1198
|
+
first_name: user.first_name,
|
|
1199
|
+
last_name: user.last_name,
|
|
1200
|
+
image_url: user.avatar_url,
|
|
1201
|
+
phone: user.phone || void 0,
|
|
1202
|
+
email: user.email || void 0
|
|
1203
|
+
},
|
|
1204
|
+
conversationName: name
|
|
1205
|
+
};
|
|
1206
|
+
});
|
|
1207
|
+
}, [apiConfig]);
|
|
1208
|
+
const searchChannels = useCallback(async (query) => {
|
|
1209
|
+
return (await searchConversations(apiConfig, { filterrific: { search_query: query } }) ?? []).map((channel) => {
|
|
1210
|
+
const { text: nameWithoutEmoji } = extractEmoji(channel.name);
|
|
1211
|
+
return {
|
|
1212
|
+
id: `channel-${channel.id}`,
|
|
1213
|
+
label: formatMessageChannelName(nameWithoutEmoji),
|
|
1214
|
+
imageType: "channel",
|
|
1215
|
+
userData: {
|
|
1216
|
+
first_name: channel.name,
|
|
1217
|
+
last_name: null,
|
|
1218
|
+
image_url: channel.avatar_url
|
|
1219
|
+
},
|
|
1220
|
+
conversationName: channel.name
|
|
1221
|
+
};
|
|
1222
|
+
});
|
|
1223
|
+
}, [apiConfig]);
|
|
1224
|
+
if (messagingAuth.isLoading) return /* @__PURE__ */ jsx("div", {
|
|
1225
|
+
...divProps,
|
|
1226
|
+
className: `flex h-full items-center justify-center ${divProps.className ?? ""}`,
|
|
1227
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1228
|
+
className: "text-muted-foreground",
|
|
1229
|
+
children: "Loading messaging..."
|
|
1230
|
+
})
|
|
1231
|
+
});
|
|
1232
|
+
if (!messagingAuth.recipientId) return /* @__PURE__ */ jsx("div", {
|
|
1233
|
+
...divProps,
|
|
1234
|
+
className: `flex h-full items-center justify-center ${divProps.className ?? ""}`,
|
|
1235
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1236
|
+
className: "border-border max-w-sm rounded-lg border border-dashed p-8 text-center",
|
|
1237
|
+
children: [/* @__PURE__ */ jsx("h2", {
|
|
1238
|
+
className: "text-foreground text-xl font-semibold",
|
|
1239
|
+
children: "Messaging"
|
|
1240
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
1241
|
+
className: "text-muted-foreground mt-2",
|
|
1242
|
+
children: "Messaging is not available for your account"
|
|
1243
|
+
})]
|
|
1244
|
+
})
|
|
1245
|
+
});
|
|
1246
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1247
|
+
...divProps,
|
|
1248
|
+
className: `h-full ${divProps.className ?? ""}`,
|
|
1249
|
+
children: /* @__PURE__ */ jsx(MessagingApp, {
|
|
1250
|
+
config: apiConfig,
|
|
1251
|
+
auth: messagingAuth,
|
|
1252
|
+
websocketUrl: effectiveWsUrl,
|
|
1253
|
+
token,
|
|
1254
|
+
renderImage,
|
|
1255
|
+
showAdminFeatures: false,
|
|
1256
|
+
onToast: effectiveToast,
|
|
1257
|
+
messagesViewProps: {
|
|
1258
|
+
uploader,
|
|
1259
|
+
saveDrafts: true,
|
|
1260
|
+
onToast: effectiveToast
|
|
1261
|
+
},
|
|
1262
|
+
newMessageViewProps: {
|
|
1263
|
+
searchUsers,
|
|
1264
|
+
searchChannels
|
|
1265
|
+
}
|
|
1266
|
+
})
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
const messagingScreenPropertySchema = {
|
|
1270
|
+
widgetType: "MessagingScreen",
|
|
1271
|
+
displayName: "Messaging Screen",
|
|
1272
|
+
tabsConfig: [{
|
|
1273
|
+
id: "styling",
|
|
1274
|
+
label: "Styling"
|
|
1275
|
+
}],
|
|
1276
|
+
fields: []
|
|
1277
|
+
};
|
|
1278
|
+
//#endregion
|
|
1279
|
+
export { toNavigationItem as C, createDefaultAuthRedirect as D, DEFAULT_AUTH_URL as E, transformThemes$1 as S, toScreenDefinition as T, createFluidClient as _, useMessagingConfig as a, getActiveThemeId$1 as b, FluidProvider as c, FluidAuthProvider as d, useFluidAuthContext as f, ApiError as g, themes_exports as h, createFluidFileUploader as i, useFluidContext as l, useThemeContext as m, MessagingScreen_exports as n, useMessagingAuth as o, FluidThemeProvider as p, messagingScreenPropertySchema as r, useFluidApi as s, MessagingScreen as t, DEFAULT_SDK_WIDGET_REGISTRY as u, isApiError as v, normalizeComponentTree as w, transformManifestToRepAppData as x, buildThemeDefinition as y };
|
|
1280
|
+
|
|
1281
|
+
//# sourceMappingURL=MessagingScreen-BklF48Fn.mjs.map
|