@proveanything/smartlinks-auth-ui 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/SmartlinksFrame/SmartlinksFrame.d.ts +11 -21
- package/dist/components/SmartlinksFrame/SmartlinksFrame.d.ts.map +1 -1
- package/dist/components/SmartlinksFrame/helpers.d.ts +19 -0
- package/dist/components/SmartlinksFrame/helpers.d.ts.map +1 -0
- package/dist/components/SmartlinksFrame/index.d.ts +2 -2
- package/dist/components/SmartlinksFrame/index.d.ts.map +1 -1
- package/dist/components/SmartlinksFrame/useIframeMessages.d.ts.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +302 -257
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +303 -256
- package/dist/index.js.map +1 -1
- package/dist/types/iframeMessages.d.ts +81 -40
- package/dist/types/iframeMessages.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -14311,6 +14311,291 @@ const AccountManagement = ({ apiEndpoint, clientId, collectionId, onError, class
|
|
|
14311
14311
|
}, className: "auth-button button-secondary", children: "Cancel" })] })] }))] }))] }));
|
|
14312
14312
|
};
|
|
14313
14313
|
|
|
14314
|
+
/**
|
|
14315
|
+
* Helper functions for SmartlinksFrame
|
|
14316
|
+
* Separated to avoid circular dependencies
|
|
14317
|
+
*/
|
|
14318
|
+
/**
|
|
14319
|
+
* Compute admin status from collection/proof roles.
|
|
14320
|
+
*/
|
|
14321
|
+
function isAdminFromRoles(user, collection, proof) {
|
|
14322
|
+
if (!user?.uid)
|
|
14323
|
+
return false;
|
|
14324
|
+
const proofRole = proof?.roles?.[user.uid];
|
|
14325
|
+
if (proofRole === 'admin' || proofRole === 'owner')
|
|
14326
|
+
return true;
|
|
14327
|
+
const collectionRole = collection?.roles?.[user.uid];
|
|
14328
|
+
if (collectionRole === 'admin' || collectionRole === 'owner')
|
|
14329
|
+
return true;
|
|
14330
|
+
return false;
|
|
14331
|
+
}
|
|
14332
|
+
/**
|
|
14333
|
+
* Build iframe src URL with query parameters.
|
|
14334
|
+
*/
|
|
14335
|
+
function buildIframeSrc(baseUrl, params) {
|
|
14336
|
+
const url = new URL(baseUrl);
|
|
14337
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
14338
|
+
if (value !== undefined) {
|
|
14339
|
+
url.searchParams.set(key, value);
|
|
14340
|
+
}
|
|
14341
|
+
});
|
|
14342
|
+
return url.toString();
|
|
14343
|
+
}
|
|
14344
|
+
|
|
14345
|
+
/**
|
|
14346
|
+
* SmartlinksFrame - React wrapper for embedding SmartLinks microapps.
|
|
14347
|
+
*
|
|
14348
|
+
* This component handles iframe creation and message passing for SmartLinks apps.
|
|
14349
|
+
* It requires only collectionId and appId - the SDK handles URL discovery and API proxying.
|
|
14350
|
+
*
|
|
14351
|
+
* @example
|
|
14352
|
+
* ```tsx
|
|
14353
|
+
* // Simple usage - SDK resolves app URL automatically
|
|
14354
|
+
* <SmartlinksFrame
|
|
14355
|
+
* collectionId="my-collection"
|
|
14356
|
+
* appId="warranty-app"
|
|
14357
|
+
* />
|
|
14358
|
+
*
|
|
14359
|
+
* // With optional product context
|
|
14360
|
+
* <SmartlinksFrame
|
|
14361
|
+
* collectionId="my-collection"
|
|
14362
|
+
* appId="warranty-app"
|
|
14363
|
+
* productId="product-123"
|
|
14364
|
+
* />
|
|
14365
|
+
* ```
|
|
14366
|
+
*/
|
|
14367
|
+
const SmartlinksFrame = ({ collectionId, appId, productId, proofId, version = 'stable', collection, product: _product, // Destructured but currently unused - kept for cache optimization
|
|
14368
|
+
proof, initialPath, onRouteChange, autoResize = true, minHeight, maxHeight, onReady, onError, className, style, }) => {
|
|
14369
|
+
const iframeRef = useRef(null);
|
|
14370
|
+
const [src, setSrc] = useState(null);
|
|
14371
|
+
const [height, setHeight] = useState(minHeight || 400);
|
|
14372
|
+
const [error, setError] = useState(null);
|
|
14373
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
14374
|
+
// Get auth context if available (optional - works without AuthProvider)
|
|
14375
|
+
const authContext = useContext(AuthContext);
|
|
14376
|
+
const user = authContext?.user ?? null;
|
|
14377
|
+
// Compute isAdmin from collection/proof roles
|
|
14378
|
+
const isAdmin = isAdminFromRoles(user ? { uid: user.uid } : null, collection, proof);
|
|
14379
|
+
// Handle messages from iframe
|
|
14380
|
+
const handleMessage = useCallback((event) => {
|
|
14381
|
+
const data = event.data;
|
|
14382
|
+
if (!data || typeof data !== 'object')
|
|
14383
|
+
return;
|
|
14384
|
+
// Handle resize messages
|
|
14385
|
+
if (data.type === 'smartlinks:resize' && autoResize) {
|
|
14386
|
+
let newHeight = data.payload?.height || data.height;
|
|
14387
|
+
if (typeof newHeight === 'number') {
|
|
14388
|
+
if (minHeight)
|
|
14389
|
+
newHeight = Math.max(newHeight, minHeight);
|
|
14390
|
+
if (maxHeight)
|
|
14391
|
+
newHeight = Math.min(newHeight, maxHeight);
|
|
14392
|
+
setHeight(newHeight);
|
|
14393
|
+
}
|
|
14394
|
+
}
|
|
14395
|
+
// Handle route change messages
|
|
14396
|
+
if (data.type === 'smartlinks-route-change' && onRouteChange) {
|
|
14397
|
+
onRouteChange(data.path || '/', data.state || {});
|
|
14398
|
+
}
|
|
14399
|
+
// Handle ready message
|
|
14400
|
+
if (data.type === 'smartlinks:ready') {
|
|
14401
|
+
setIsLoading(false);
|
|
14402
|
+
onReady?.();
|
|
14403
|
+
}
|
|
14404
|
+
}, [autoResize, minHeight, maxHeight, onRouteChange, onReady]);
|
|
14405
|
+
// Set up message listener
|
|
14406
|
+
useEffect(() => {
|
|
14407
|
+
window.addEventListener('message', handleMessage);
|
|
14408
|
+
return () => window.removeEventListener('message', handleMessage);
|
|
14409
|
+
}, [handleMessage]);
|
|
14410
|
+
// Resolve app URL and set iframe src
|
|
14411
|
+
useEffect(() => {
|
|
14412
|
+
setError(null);
|
|
14413
|
+
setIsLoading(true);
|
|
14414
|
+
setSrc(null);
|
|
14415
|
+
// Try to get app URL from collection apps config
|
|
14416
|
+
const resolveAppUrl = async () => {
|
|
14417
|
+
try {
|
|
14418
|
+
// Use SDK to get collection data which may include apps config
|
|
14419
|
+
const collectionData = await smartlinks.collection.get(collectionId);
|
|
14420
|
+
const collectionAsAny = collectionData;
|
|
14421
|
+
const apps = collectionAsAny.apps ?? [];
|
|
14422
|
+
const app = apps.find((a) => a.id === appId);
|
|
14423
|
+
if (!app?.url) {
|
|
14424
|
+
throw new Error(`App "${appId}" not found in collection "${collectionId}"`);
|
|
14425
|
+
}
|
|
14426
|
+
// Get the appropriate URL based on version
|
|
14427
|
+
let baseUrl = app.url;
|
|
14428
|
+
if (version === 'development' && app.versions?.development) {
|
|
14429
|
+
baseUrl = app.versions.development;
|
|
14430
|
+
}
|
|
14431
|
+
else if (version === 'stable' && app.versions?.stable) {
|
|
14432
|
+
baseUrl = app.versions.stable;
|
|
14433
|
+
}
|
|
14434
|
+
// Build iframe URL with context params
|
|
14435
|
+
const iframeSrc = buildIframeSrc(baseUrl, {
|
|
14436
|
+
collectionId,
|
|
14437
|
+
appId,
|
|
14438
|
+
productId,
|
|
14439
|
+
proofId,
|
|
14440
|
+
isAdmin: isAdmin ? '1' : '0',
|
|
14441
|
+
dark: document.documentElement.classList.contains('dark') ? '1' : '0',
|
|
14442
|
+
});
|
|
14443
|
+
// Add initial path if provided
|
|
14444
|
+
const finalSrc = initialPath
|
|
14445
|
+
? `${iframeSrc}#${initialPath}`
|
|
14446
|
+
: iframeSrc;
|
|
14447
|
+
setSrc(finalSrc);
|
|
14448
|
+
setIsLoading(false);
|
|
14449
|
+
}
|
|
14450
|
+
catch (err) {
|
|
14451
|
+
const errorMsg = err instanceof Error ? err.message : 'Failed to resolve app URL';
|
|
14452
|
+
console.error('[SmartlinksFrame] Error:', errorMsg);
|
|
14453
|
+
setError(errorMsg);
|
|
14454
|
+
setIsLoading(false);
|
|
14455
|
+
onError?.(err instanceof Error ? err : new Error(errorMsg));
|
|
14456
|
+
}
|
|
14457
|
+
};
|
|
14458
|
+
resolveAppUrl();
|
|
14459
|
+
}, [collectionId, appId, productId, proofId, version, initialPath, isAdmin, onError]);
|
|
14460
|
+
// Calculate container style
|
|
14461
|
+
const containerStyle = {
|
|
14462
|
+
position: 'relative',
|
|
14463
|
+
height: autoResize ? `${height}px` : '100%',
|
|
14464
|
+
width: '100%',
|
|
14465
|
+
...style,
|
|
14466
|
+
};
|
|
14467
|
+
return (jsxs("div", { className: className, style: containerStyle, children: [error && (jsxs("div", { style: {
|
|
14468
|
+
padding: '0.5rem 1rem',
|
|
14469
|
+
backgroundColor: '#fee2e2',
|
|
14470
|
+
color: '#dc2626',
|
|
14471
|
+
borderBottom: '1px solid #fecaca',
|
|
14472
|
+
fontSize: '0.875rem',
|
|
14473
|
+
}, children: ["Error: ", error] })), isLoading && !error && (jsx("div", { style: {
|
|
14474
|
+
display: 'flex',
|
|
14475
|
+
alignItems: 'center',
|
|
14476
|
+
justifyContent: 'center',
|
|
14477
|
+
height: '100%',
|
|
14478
|
+
minHeight: minHeight || 200,
|
|
14479
|
+
color: '#6b7280',
|
|
14480
|
+
}, children: "Loading..." })), src && (jsx("iframe", { ref: iframeRef, src: src, frameBorder: 0, width: "100%", height: "100%", allow: "camera;microphone;fullscreen;geolocation;identity-credentials-get", style: {
|
|
14481
|
+
display: isLoading ? 'none' : 'block',
|
|
14482
|
+
width: '100%',
|
|
14483
|
+
height: autoResize ? `${height}px` : '100%',
|
|
14484
|
+
border: 'none',
|
|
14485
|
+
} }))] }));
|
|
14486
|
+
};
|
|
14487
|
+
|
|
14488
|
+
/**
|
|
14489
|
+
* Hook to detect if the current user is an admin of the collection or proof.
|
|
14490
|
+
*
|
|
14491
|
+
* Admin status is determined by checking if the user's UID exists in the
|
|
14492
|
+
* `roles` object of either the collection or proof.
|
|
14493
|
+
*
|
|
14494
|
+
* @param collection - Collection object with optional roles
|
|
14495
|
+
* @param proof - Proof object with optional roles
|
|
14496
|
+
* @param user - Current authenticated user
|
|
14497
|
+
* @returns boolean indicating admin status
|
|
14498
|
+
*/
|
|
14499
|
+
function useAdminDetection(collection, proof, user) {
|
|
14500
|
+
return useMemo(() => {
|
|
14501
|
+
if (!user?.uid)
|
|
14502
|
+
return false;
|
|
14503
|
+
// Check collection roles
|
|
14504
|
+
if (collection?.roles && collection.roles[user.uid]) {
|
|
14505
|
+
return true;
|
|
14506
|
+
}
|
|
14507
|
+
// Check proof roles (for proof-level admin)
|
|
14508
|
+
if (proof?.roles && proof.roles[user.uid]) {
|
|
14509
|
+
return true;
|
|
14510
|
+
}
|
|
14511
|
+
return false;
|
|
14512
|
+
}, [collection?.roles, proof?.roles, user?.uid]);
|
|
14513
|
+
}
|
|
14514
|
+
|
|
14515
|
+
/**
|
|
14516
|
+
* Hook to handle iframe height management.
|
|
14517
|
+
*
|
|
14518
|
+
* Supports two modes:
|
|
14519
|
+
* 1. Viewport-based: Calculates height based on iframe position and viewport
|
|
14520
|
+
* 2. Content-based: Listens for smartlinks:resize messages from the iframe
|
|
14521
|
+
*
|
|
14522
|
+
* @param iframeRef - Ref to the iframe element
|
|
14523
|
+
* @param options - Resize configuration options
|
|
14524
|
+
* @returns Current calculated height in pixels
|
|
14525
|
+
*/
|
|
14526
|
+
function useIframeResize(iframeRef, options = {}) {
|
|
14527
|
+
const { autoResize = true, minHeight, maxHeight } = options;
|
|
14528
|
+
const [height, setHeight] = useState(0);
|
|
14529
|
+
// Calculate height based on viewport position
|
|
14530
|
+
const calculateHeight = useCallback(() => {
|
|
14531
|
+
const iframe = iframeRef.current;
|
|
14532
|
+
if (!iframe)
|
|
14533
|
+
return;
|
|
14534
|
+
const container = iframe.parentElement;
|
|
14535
|
+
if (!container)
|
|
14536
|
+
return;
|
|
14537
|
+
const rect = container.getBoundingClientRect();
|
|
14538
|
+
const viewportHeight = window.innerHeight;
|
|
14539
|
+
let calculatedHeight = Math.max(0, viewportHeight - rect.top);
|
|
14540
|
+
// Apply constraints
|
|
14541
|
+
if (minHeight !== undefined) {
|
|
14542
|
+
calculatedHeight = Math.max(calculatedHeight, minHeight);
|
|
14543
|
+
}
|
|
14544
|
+
if (maxHeight !== undefined) {
|
|
14545
|
+
calculatedHeight = Math.min(calculatedHeight, maxHeight);
|
|
14546
|
+
}
|
|
14547
|
+
setHeight(calculatedHeight);
|
|
14548
|
+
}, [minHeight, maxHeight, iframeRef]);
|
|
14549
|
+
// Handle viewport resize events
|
|
14550
|
+
useEffect(() => {
|
|
14551
|
+
if (!autoResize)
|
|
14552
|
+
return;
|
|
14553
|
+
// Initial calculation with multiple retries for late layout
|
|
14554
|
+
calculateHeight();
|
|
14555
|
+
const t1 = setTimeout(calculateHeight, 10);
|
|
14556
|
+
const t2 = setTimeout(calculateHeight, 100);
|
|
14557
|
+
const t3 = setTimeout(calculateHeight, 500);
|
|
14558
|
+
const handleResize = () => calculateHeight();
|
|
14559
|
+
window.addEventListener('resize', handleResize);
|
|
14560
|
+
window.addEventListener('orientationchange', handleResize);
|
|
14561
|
+
return () => {
|
|
14562
|
+
clearTimeout(t1);
|
|
14563
|
+
clearTimeout(t2);
|
|
14564
|
+
clearTimeout(t3);
|
|
14565
|
+
window.removeEventListener('resize', handleResize);
|
|
14566
|
+
window.removeEventListener('orientationchange', handleResize);
|
|
14567
|
+
};
|
|
14568
|
+
}, [autoResize, calculateHeight]);
|
|
14569
|
+
// Listen for content-based resize messages from iframe
|
|
14570
|
+
useEffect(() => {
|
|
14571
|
+
const handleMessage = (event) => {
|
|
14572
|
+
// Validate source is our iframe
|
|
14573
|
+
if (iframeRef.current && event.source !== iframeRef.current.contentWindow) {
|
|
14574
|
+
return;
|
|
14575
|
+
}
|
|
14576
|
+
const data = event.data;
|
|
14577
|
+
// Handle smartlinks:resize message
|
|
14578
|
+
if (data?._smartlinksIframeMessage && data?.type === 'smartlinks:resize') {
|
|
14579
|
+
const contentHeight = data.payload?.height;
|
|
14580
|
+
if (typeof contentHeight === 'number' && contentHeight > 0) {
|
|
14581
|
+
let newHeight = contentHeight;
|
|
14582
|
+
// Apply constraints
|
|
14583
|
+
if (minHeight !== undefined) {
|
|
14584
|
+
newHeight = Math.max(newHeight, minHeight);
|
|
14585
|
+
}
|
|
14586
|
+
if (maxHeight !== undefined) {
|
|
14587
|
+
newHeight = Math.min(newHeight, maxHeight);
|
|
14588
|
+
}
|
|
14589
|
+
setHeight(newHeight);
|
|
14590
|
+
}
|
|
14591
|
+
}
|
|
14592
|
+
};
|
|
14593
|
+
window.addEventListener('message', handleMessage);
|
|
14594
|
+
return () => window.removeEventListener('message', handleMessage);
|
|
14595
|
+
}, [iframeRef, minHeight, maxHeight]);
|
|
14596
|
+
return height;
|
|
14597
|
+
}
|
|
14598
|
+
|
|
14314
14599
|
// Re-import the constant since we can't import from types
|
|
14315
14600
|
const KNOWN_PARAMS = new Set([
|
|
14316
14601
|
'collectionId',
|
|
@@ -14381,12 +14666,12 @@ function useIframeMessages(iframeRef, options) {
|
|
|
14381
14666
|
// Filter out known iframe params, keep only app-specific state
|
|
14382
14667
|
const filteredState = {};
|
|
14383
14668
|
Object.entries(context).forEach(([key, value]) => {
|
|
14384
|
-
if (value != null && !KNOWN_PARAMS.has(key)) {
|
|
14669
|
+
if (value != null && !KNOWN_PARAMS.has(key) && typeof value === 'string') {
|
|
14385
14670
|
filteredState[key] = value;
|
|
14386
14671
|
}
|
|
14387
14672
|
});
|
|
14388
14673
|
Object.entries(state).forEach(([key, value]) => {
|
|
14389
|
-
if (value != null && !KNOWN_PARAMS.has(key)) {
|
|
14674
|
+
if (value != null && !KNOWN_PARAMS.has(key) && typeof value === 'string') {
|
|
14390
14675
|
filteredState[key] = value;
|
|
14391
14676
|
}
|
|
14392
14677
|
});
|
|
@@ -14402,7 +14687,7 @@ function useIframeMessages(iframeRef, options) {
|
|
|
14402
14687
|
if ('_smartlinksCustomProxyRequest' in data && data._smartlinksCustomProxyRequest) {
|
|
14403
14688
|
if (data.request === 'REDIRECT') {
|
|
14404
14689
|
const url = data.params?.url;
|
|
14405
|
-
if (url) {
|
|
14690
|
+
if (typeof url === 'string') {
|
|
14406
14691
|
window.location.href = url;
|
|
14407
14692
|
}
|
|
14408
14693
|
response.data = { success: true };
|
|
@@ -14524,9 +14809,20 @@ function useIframeMessages(iframeRef, options) {
|
|
|
14524
14809
|
try {
|
|
14525
14810
|
// Validate token using SDK
|
|
14526
14811
|
console.log('[SmartlinksFrame] Validating auth token...');
|
|
14527
|
-
|
|
14812
|
+
if (typeof token === 'string') {
|
|
14813
|
+
await smartlinks.auth.verifyToken(token);
|
|
14814
|
+
}
|
|
14815
|
+
// Cast user to expected shape
|
|
14816
|
+
const userPayload = iframeUser;
|
|
14817
|
+
// Validate user object has required properties
|
|
14818
|
+
const authUser = {
|
|
14819
|
+
uid: userPayload?.uid || '',
|
|
14820
|
+
email: userPayload?.email,
|
|
14821
|
+
displayName: userPayload?.displayName,
|
|
14822
|
+
accountData: accountData || userPayload?.accountData,
|
|
14823
|
+
};
|
|
14528
14824
|
// Use AuthProvider's login to persist session
|
|
14529
|
-
await loginRef.current(token,
|
|
14825
|
+
await loginRef.current(token, authUser, accountData);
|
|
14530
14826
|
// Send acknowledgment
|
|
14531
14827
|
sendResponse(event.source, event.origin, {
|
|
14532
14828
|
type: 'smartlinks:authkit:login-acknowledged',
|
|
@@ -14682,257 +14978,6 @@ function useIframeMessages(iframeRef, options) {
|
|
|
14682
14978
|
}, [iframeRef, handleRouteChange, handleStandardMessage, handleUpload, handleProxyRequest]);
|
|
14683
14979
|
}
|
|
14684
14980
|
|
|
14685
|
-
/**
|
|
14686
|
-
* Hook to handle iframe height management.
|
|
14687
|
-
*
|
|
14688
|
-
* Supports two modes:
|
|
14689
|
-
* 1. Viewport-based: Calculates height based on iframe position and viewport
|
|
14690
|
-
* 2. Content-based: Listens for smartlinks:resize messages from the iframe
|
|
14691
|
-
*
|
|
14692
|
-
* @param iframeRef - Ref to the iframe element
|
|
14693
|
-
* @param options - Resize configuration options
|
|
14694
|
-
* @returns Current calculated height in pixels
|
|
14695
|
-
*/
|
|
14696
|
-
function useIframeResize(iframeRef, options = {}) {
|
|
14697
|
-
const { autoResize = true, minHeight, maxHeight } = options;
|
|
14698
|
-
const [height, setHeight] = useState(0);
|
|
14699
|
-
// Calculate height based on viewport position
|
|
14700
|
-
const calculateHeight = useCallback(() => {
|
|
14701
|
-
const iframe = iframeRef.current;
|
|
14702
|
-
if (!iframe)
|
|
14703
|
-
return;
|
|
14704
|
-
const container = iframe.parentElement;
|
|
14705
|
-
if (!container)
|
|
14706
|
-
return;
|
|
14707
|
-
const rect = container.getBoundingClientRect();
|
|
14708
|
-
const viewportHeight = window.innerHeight;
|
|
14709
|
-
let calculatedHeight = Math.max(0, viewportHeight - rect.top);
|
|
14710
|
-
// Apply constraints
|
|
14711
|
-
if (minHeight !== undefined) {
|
|
14712
|
-
calculatedHeight = Math.max(calculatedHeight, minHeight);
|
|
14713
|
-
}
|
|
14714
|
-
if (maxHeight !== undefined) {
|
|
14715
|
-
calculatedHeight = Math.min(calculatedHeight, maxHeight);
|
|
14716
|
-
}
|
|
14717
|
-
setHeight(calculatedHeight);
|
|
14718
|
-
}, [minHeight, maxHeight, iframeRef]);
|
|
14719
|
-
// Handle viewport resize events
|
|
14720
|
-
useEffect(() => {
|
|
14721
|
-
if (!autoResize)
|
|
14722
|
-
return;
|
|
14723
|
-
// Initial calculation with multiple retries for late layout
|
|
14724
|
-
calculateHeight();
|
|
14725
|
-
const t1 = setTimeout(calculateHeight, 10);
|
|
14726
|
-
const t2 = setTimeout(calculateHeight, 100);
|
|
14727
|
-
const t3 = setTimeout(calculateHeight, 500);
|
|
14728
|
-
const handleResize = () => calculateHeight();
|
|
14729
|
-
window.addEventListener('resize', handleResize);
|
|
14730
|
-
window.addEventListener('orientationchange', handleResize);
|
|
14731
|
-
return () => {
|
|
14732
|
-
clearTimeout(t1);
|
|
14733
|
-
clearTimeout(t2);
|
|
14734
|
-
clearTimeout(t3);
|
|
14735
|
-
window.removeEventListener('resize', handleResize);
|
|
14736
|
-
window.removeEventListener('orientationchange', handleResize);
|
|
14737
|
-
};
|
|
14738
|
-
}, [autoResize, calculateHeight]);
|
|
14739
|
-
// Listen for content-based resize messages from iframe
|
|
14740
|
-
useEffect(() => {
|
|
14741
|
-
const handleMessage = (event) => {
|
|
14742
|
-
// Validate source is our iframe
|
|
14743
|
-
if (iframeRef.current && event.source !== iframeRef.current.contentWindow) {
|
|
14744
|
-
return;
|
|
14745
|
-
}
|
|
14746
|
-
const data = event.data;
|
|
14747
|
-
// Handle smartlinks:resize message
|
|
14748
|
-
if (data?._smartlinksIframeMessage && data?.type === 'smartlinks:resize') {
|
|
14749
|
-
const contentHeight = data.payload?.height;
|
|
14750
|
-
if (typeof contentHeight === 'number' && contentHeight > 0) {
|
|
14751
|
-
let newHeight = contentHeight;
|
|
14752
|
-
// Apply constraints
|
|
14753
|
-
if (minHeight !== undefined) {
|
|
14754
|
-
newHeight = Math.max(newHeight, minHeight);
|
|
14755
|
-
}
|
|
14756
|
-
if (maxHeight !== undefined) {
|
|
14757
|
-
newHeight = Math.min(newHeight, maxHeight);
|
|
14758
|
-
}
|
|
14759
|
-
setHeight(newHeight);
|
|
14760
|
-
}
|
|
14761
|
-
}
|
|
14762
|
-
};
|
|
14763
|
-
window.addEventListener('message', handleMessage);
|
|
14764
|
-
return () => window.removeEventListener('message', handleMessage);
|
|
14765
|
-
}, [iframeRef, minHeight, maxHeight]);
|
|
14766
|
-
return height;
|
|
14767
|
-
}
|
|
14768
|
-
|
|
14769
|
-
/**
|
|
14770
|
-
* Hook to detect if the current user is an admin of the collection or proof.
|
|
14771
|
-
*
|
|
14772
|
-
* Admin status is determined by checking if the user's UID exists in the
|
|
14773
|
-
* `roles` object of either the collection or proof.
|
|
14774
|
-
*
|
|
14775
|
-
* @param collection - Collection object with optional roles
|
|
14776
|
-
* @param proof - Proof object with optional roles
|
|
14777
|
-
* @param user - Current authenticated user
|
|
14778
|
-
* @returns boolean indicating admin status
|
|
14779
|
-
*/
|
|
14780
|
-
function useAdminDetection(collection, proof, user) {
|
|
14781
|
-
return useMemo(() => {
|
|
14782
|
-
if (!user?.uid)
|
|
14783
|
-
return false;
|
|
14784
|
-
// Check collection roles
|
|
14785
|
-
if (collection?.roles && collection.roles[user.uid]) {
|
|
14786
|
-
return true;
|
|
14787
|
-
}
|
|
14788
|
-
// Check proof roles (for proof-level admin)
|
|
14789
|
-
if (proof?.roles && proof.roles[user.uid]) {
|
|
14790
|
-
return true;
|
|
14791
|
-
}
|
|
14792
|
-
return false;
|
|
14793
|
-
}, [collection?.roles, proof?.roles, user?.uid]);
|
|
14794
|
-
}
|
|
14795
|
-
|
|
14796
|
-
/**
|
|
14797
|
-
* SmartlinksFrame - Parent-side iframe wrapper for embedding SmartLinks microapps.
|
|
14798
|
-
*
|
|
14799
|
-
* This component handles all bidirectional communication with the embedded iframe:
|
|
14800
|
-
* - API proxy requests (with optional caching)
|
|
14801
|
-
* - Authentication state synchronization (when inside AuthProvider)
|
|
14802
|
-
* - Deep linking / route changes
|
|
14803
|
-
* - Resize management
|
|
14804
|
-
* - File upload proxying
|
|
14805
|
-
*
|
|
14806
|
-
* Can be used with or without AuthProvider - authentication is optional.
|
|
14807
|
-
*
|
|
14808
|
-
* @example
|
|
14809
|
-
* ```tsx
|
|
14810
|
-
* // With authentication
|
|
14811
|
-
* <AuthProvider collectionId="my-collection">
|
|
14812
|
-
* <SmartlinksFrame
|
|
14813
|
-
* collectionId="my-collection"
|
|
14814
|
-
* appId="warranty-app"
|
|
14815
|
-
* appUrl="https://warranty.lovable.app"
|
|
14816
|
-
* collection={collectionData}
|
|
14817
|
-
* />
|
|
14818
|
-
* </AuthProvider>
|
|
14819
|
-
*
|
|
14820
|
-
* // Without authentication (public/anonymous access)
|
|
14821
|
-
* <SmartlinksFrame
|
|
14822
|
-
* collectionId="my-collection"
|
|
14823
|
-
* appId="info-app"
|
|
14824
|
-
* appUrl="https://info-app.lovable.app"
|
|
14825
|
-
* />
|
|
14826
|
-
* ```
|
|
14827
|
-
*/
|
|
14828
|
-
const SmartlinksFrame = ({ collectionId, appId, productId, proofId, appUrl, version = 'stable', collection, product, proof, initialPath, onRouteChange, autoResize = true, minHeight, maxHeight, onReady, onError, className, style, }) => {
|
|
14829
|
-
const iframeRef = useRef(null);
|
|
14830
|
-
// Get auth context if available (optional - SmartlinksFrame works without AuthProvider)
|
|
14831
|
-
const authContext = useContext(AuthContext);
|
|
14832
|
-
const user = authContext?.user ?? null;
|
|
14833
|
-
const login = authContext?.login;
|
|
14834
|
-
const logout = authContext?.logout;
|
|
14835
|
-
// Compute isAdmin from collection/proof roles
|
|
14836
|
-
const isAdmin = useAdminDetection(collection, proof, user);
|
|
14837
|
-
// Handle resize
|
|
14838
|
-
const height = useIframeResize(iframeRef, { autoResize, minHeight, maxHeight });
|
|
14839
|
-
// Handle all iframe messages
|
|
14840
|
-
useIframeMessages(iframeRef, {
|
|
14841
|
-
collectionId,
|
|
14842
|
-
productId,
|
|
14843
|
-
proofId,
|
|
14844
|
-
cachedData: { collection, product, proof, user },
|
|
14845
|
-
login,
|
|
14846
|
-
logout,
|
|
14847
|
-
onRouteChange,
|
|
14848
|
-
onError,
|
|
14849
|
-
});
|
|
14850
|
-
// Build iframe URL with all context params
|
|
14851
|
-
const frameSrc = useMemo(() => {
|
|
14852
|
-
const params = new URLSearchParams();
|
|
14853
|
-
// Required context
|
|
14854
|
-
params.set('collectionId', collectionId);
|
|
14855
|
-
params.set('appId', appId);
|
|
14856
|
-
// Optional context
|
|
14857
|
-
if (productId)
|
|
14858
|
-
params.set('productId', productId);
|
|
14859
|
-
if (proofId)
|
|
14860
|
-
params.set('proofId', proofId);
|
|
14861
|
-
if (isAdmin)
|
|
14862
|
-
params.set('isAdmin', 'true');
|
|
14863
|
-
// Dark mode from collection
|
|
14864
|
-
const isDark = collection?.dark ?? false;
|
|
14865
|
-
params.set('dark', isDark ? '1' : '0');
|
|
14866
|
-
// Include parent URL for downstream redirects/context
|
|
14867
|
-
try {
|
|
14868
|
-
params.set('parentUrl', window.location.href);
|
|
14869
|
-
}
|
|
14870
|
-
catch {
|
|
14871
|
-
// Ignore if can't access location
|
|
14872
|
-
}
|
|
14873
|
-
// Encode theme from collection if available
|
|
14874
|
-
if (collection) {
|
|
14875
|
-
try {
|
|
14876
|
-
const themeData = {
|
|
14877
|
-
p: collection.primaryColor,
|
|
14878
|
-
s: collection.secondaryColor,
|
|
14879
|
-
m: collection.dark ? 'd' : 'l',
|
|
14880
|
-
};
|
|
14881
|
-
// Only include if we have meaningful data
|
|
14882
|
-
if (themeData.p || themeData.s) {
|
|
14883
|
-
params.set('theme', btoa(JSON.stringify(themeData)));
|
|
14884
|
-
}
|
|
14885
|
-
}
|
|
14886
|
-
catch {
|
|
14887
|
-
// Ignore encoding errors
|
|
14888
|
-
}
|
|
14889
|
-
}
|
|
14890
|
-
// Determine base URL
|
|
14891
|
-
let base = appUrl;
|
|
14892
|
-
// If appUrl doesn't look like a full URL, construct from smartlinks.app
|
|
14893
|
-
if (!appUrl.startsWith('http')) {
|
|
14894
|
-
base = `https://smartlinks.app/apps/${appId}/${version}`;
|
|
14895
|
-
}
|
|
14896
|
-
// Clean up base URL
|
|
14897
|
-
base = base.replace(/#\/?$/, ''); // Remove trailing hash
|
|
14898
|
-
if (base.endsWith('/')) {
|
|
14899
|
-
base = base.slice(0, -1);
|
|
14900
|
-
}
|
|
14901
|
-
// Build hash path
|
|
14902
|
-
let hashPath = initialPath || '';
|
|
14903
|
-
if (hashPath && !hashPath.startsWith('/')) {
|
|
14904
|
-
hashPath = '/' + hashPath;
|
|
14905
|
-
}
|
|
14906
|
-
if (hashPath === '/') {
|
|
14907
|
-
hashPath = '';
|
|
14908
|
-
}
|
|
14909
|
-
// Construct final URL: base#/path?params
|
|
14910
|
-
return `${base}/#/${hashPath}?${params.toString()}`.replace('/#//', '/#/');
|
|
14911
|
-
}, [
|
|
14912
|
-
collectionId,
|
|
14913
|
-
appId,
|
|
14914
|
-
productId,
|
|
14915
|
-
proofId,
|
|
14916
|
-
appUrl,
|
|
14917
|
-
version,
|
|
14918
|
-
initialPath,
|
|
14919
|
-
isAdmin,
|
|
14920
|
-
collection,
|
|
14921
|
-
]);
|
|
14922
|
-
// Calculate container style
|
|
14923
|
-
const containerStyle = {
|
|
14924
|
-
height: autoResize && height > 0 ? `${height}px` : '100%',
|
|
14925
|
-
width: '100%',
|
|
14926
|
-
...style,
|
|
14927
|
-
};
|
|
14928
|
-
return (jsx("div", { className: className, style: containerStyle, children: jsx("iframe", { ref: iframeRef, src: frameSrc, frameBorder: 0, width: "100%", height: "100%", allow: "camera;microphone;fullscreen;geolocation;identity-credentials-get", onLoad: onReady, style: {
|
|
14929
|
-
display: 'block',
|
|
14930
|
-
width: '100%',
|
|
14931
|
-
height: '100%',
|
|
14932
|
-
border: 0,
|
|
14933
|
-
} }) }));
|
|
14934
|
-
};
|
|
14935
|
-
|
|
14936
14981
|
const ProtectedRoute = ({ children, fallback, redirectTo, }) => {
|
|
14937
14982
|
const { isAuthenticated, isLoading } = useAuth();
|
|
14938
14983
|
// Show loading state
|
|
@@ -15003,5 +15048,5 @@ async function setDefaultAuthKitId(collectionId, authKitId) {
|
|
|
15003
15048
|
});
|
|
15004
15049
|
}
|
|
15005
15050
|
|
|
15006
|
-
export { AccountManagement, AuthProvider, AuthUIPreview, SmartlinksAuthUI as FirebaseAuthUI, ProtectedRoute, SchemaFieldRenderer, SmartlinksAuthUI, SmartlinksFrame, getDefaultAuthKitId, getEditableFields, getErrorCode, getErrorStatusCode, getFriendlyErrorMessage, getRegistrationFields, isAuthError, isConflictError, isRateLimitError, isServerError, setDefaultAuthKitId, sortFieldsByPlacement, tokenStorage, useAdminDetection, useAuth, useIframeMessages, useIframeResize };
|
|
15051
|
+
export { AccountManagement, AuthProvider, AuthUIPreview, SmartlinksAuthUI as FirebaseAuthUI, ProtectedRoute, SchemaFieldRenderer, SmartlinksAuthUI, SmartlinksFrame, buildIframeSrc, getDefaultAuthKitId, getEditableFields, getErrorCode, getErrorStatusCode, getFriendlyErrorMessage, getRegistrationFields, isAdminFromRoles, isAuthError, isConflictError, isRateLimitError, isServerError, setDefaultAuthKitId, sortFieldsByPlacement, tokenStorage, useAdminDetection, useAuth, useIframeMessages, useIframeResize };
|
|
15007
15052
|
//# sourceMappingURL=index.esm.js.map
|