@proveanything/smartlinks-auth-ui 0.3.0 → 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 +26 -0
- package/dist/components/SmartlinksFrame/SmartlinksFrame.d.ts.map +1 -0
- 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 +7 -0
- package/dist/components/SmartlinksFrame/index.d.ts.map +1 -0
- package/dist/components/SmartlinksFrame/useAdminDetection.d.ts +14 -0
- package/dist/components/SmartlinksFrame/useAdminDetection.d.ts.map +1 -0
- package/dist/components/SmartlinksFrame/useIframeMessages.d.ts +27 -0
- package/dist/components/SmartlinksFrame/useIframeMessages.d.ts.map +1 -0
- package/dist/components/SmartlinksFrame/useIframeResize.d.ts +18 -0
- package/dist/components/SmartlinksFrame/useIframeResize.d.ts.map +1 -0
- package/dist/context/AuthContext.d.ts +2 -1
- package/dist/context/AuthContext.d.ts.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +669 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +674 -0
- package/dist/index.js.map +1 -1
- package/dist/types/iframeMessages.d.ts +163 -0
- package/dist/types/iframeMessages.d.ts.map +1 -0
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -11427,6 +11427,7 @@ const tokenStorage = {
|
|
|
11427
11427
|
},
|
|
11428
11428
|
};
|
|
11429
11429
|
|
|
11430
|
+
// Export context for optional usage (e.g., SmartlinksFrame can work without AuthProvider)
|
|
11430
11431
|
const AuthContext = createContext(undefined);
|
|
11431
11432
|
const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 * 1000, preloadAccountInfo = false,
|
|
11432
11433
|
// Token refresh settings
|
|
@@ -14310,6 +14311,673 @@ const AccountManagement = ({ apiEndpoint, clientId, collectionId, onError, class
|
|
|
14310
14311
|
}, className: "auth-button button-secondary", children: "Cancel" })] })] }))] }))] }));
|
|
14311
14312
|
};
|
|
14312
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
|
+
|
|
14599
|
+
// Re-import the constant since we can't import from types
|
|
14600
|
+
const KNOWN_PARAMS = new Set([
|
|
14601
|
+
'collectionId',
|
|
14602
|
+
'appId',
|
|
14603
|
+
'productId',
|
|
14604
|
+
'proofId',
|
|
14605
|
+
'isAdmin',
|
|
14606
|
+
'dark',
|
|
14607
|
+
'parentUrl',
|
|
14608
|
+
'theme',
|
|
14609
|
+
'lang',
|
|
14610
|
+
]);
|
|
14611
|
+
/**
|
|
14612
|
+
* Hook to handle all iframe postMessage communication.
|
|
14613
|
+
*
|
|
14614
|
+
* Handles:
|
|
14615
|
+
* - Route changes (deep linking)
|
|
14616
|
+
* - API proxy requests
|
|
14617
|
+
* - Auth messages (login/logout)
|
|
14618
|
+
* - File uploads (chunked)
|
|
14619
|
+
* - Redirects
|
|
14620
|
+
*
|
|
14621
|
+
* @param iframeRef - Ref to the iframe element
|
|
14622
|
+
* @param options - Configuration and callbacks
|
|
14623
|
+
*/
|
|
14624
|
+
function useIframeMessages(iframeRef, options) {
|
|
14625
|
+
const { collectionId, productId, proofId, cachedData, login, logout, onRouteChange, onError } = options;
|
|
14626
|
+
// Track uploads in progress
|
|
14627
|
+
const uploadsRef = useRef(new Map());
|
|
14628
|
+
// Track initial load to skip first route change
|
|
14629
|
+
const isInitialLoadRef = useRef(true);
|
|
14630
|
+
// Stable refs to avoid dependency issues
|
|
14631
|
+
const cachedDataRef = useRef(cachedData);
|
|
14632
|
+
const onRouteChangeRef = useRef(onRouteChange);
|
|
14633
|
+
const onErrorRef = useRef(onError);
|
|
14634
|
+
const loginRef = useRef(login);
|
|
14635
|
+
const logoutRef = useRef(logout);
|
|
14636
|
+
// Keep refs in sync
|
|
14637
|
+
useEffect(() => {
|
|
14638
|
+
cachedDataRef.current = cachedData;
|
|
14639
|
+
}, [cachedData]);
|
|
14640
|
+
useEffect(() => {
|
|
14641
|
+
onRouteChangeRef.current = onRouteChange;
|
|
14642
|
+
}, [onRouteChange]);
|
|
14643
|
+
useEffect(() => {
|
|
14644
|
+
onErrorRef.current = onError;
|
|
14645
|
+
}, [onError]);
|
|
14646
|
+
useEffect(() => {
|
|
14647
|
+
loginRef.current = login;
|
|
14648
|
+
}, [login]);
|
|
14649
|
+
useEffect(() => {
|
|
14650
|
+
logoutRef.current = logout;
|
|
14651
|
+
}, [logout]);
|
|
14652
|
+
// Send response back to iframe
|
|
14653
|
+
const sendResponse = useCallback((source, origin, message) => {
|
|
14654
|
+
if (source && 'postMessage' in source) {
|
|
14655
|
+
source.postMessage(message, origin);
|
|
14656
|
+
}
|
|
14657
|
+
}, []);
|
|
14658
|
+
// Handle route change messages (deep linking)
|
|
14659
|
+
const handleRouteChange = useCallback((data) => {
|
|
14660
|
+
// Skip initial load to prevent duplicating path
|
|
14661
|
+
if (isInitialLoadRef.current) {
|
|
14662
|
+
isInitialLoadRef.current = false;
|
|
14663
|
+
return;
|
|
14664
|
+
}
|
|
14665
|
+
const { context = {}, state = {}, path = '' } = data;
|
|
14666
|
+
// Filter out known iframe params, keep only app-specific state
|
|
14667
|
+
const filteredState = {};
|
|
14668
|
+
Object.entries(context).forEach(([key, value]) => {
|
|
14669
|
+
if (value != null && !KNOWN_PARAMS.has(key) && typeof value === 'string') {
|
|
14670
|
+
filteredState[key] = value;
|
|
14671
|
+
}
|
|
14672
|
+
});
|
|
14673
|
+
Object.entries(state).forEach(([key, value]) => {
|
|
14674
|
+
if (value != null && !KNOWN_PARAMS.has(key) && typeof value === 'string') {
|
|
14675
|
+
filteredState[key] = value;
|
|
14676
|
+
}
|
|
14677
|
+
});
|
|
14678
|
+
onRouteChangeRef.current?.(path, filteredState);
|
|
14679
|
+
}, []);
|
|
14680
|
+
// Handle API proxy requests
|
|
14681
|
+
const handleProxyRequest = useCallback(async (data, event) => {
|
|
14682
|
+
const response = {
|
|
14683
|
+
_smartlinksProxyResponse: true,
|
|
14684
|
+
id: data.id,
|
|
14685
|
+
};
|
|
14686
|
+
// Handle custom proxy requests (redirects, etc.)
|
|
14687
|
+
if ('_smartlinksCustomProxyRequest' in data && data._smartlinksCustomProxyRequest) {
|
|
14688
|
+
if (data.request === 'REDIRECT') {
|
|
14689
|
+
const url = data.params?.url;
|
|
14690
|
+
if (typeof url === 'string') {
|
|
14691
|
+
window.location.href = url;
|
|
14692
|
+
}
|
|
14693
|
+
response.data = { success: true };
|
|
14694
|
+
sendResponse(event.source, event.origin, response);
|
|
14695
|
+
return;
|
|
14696
|
+
}
|
|
14697
|
+
}
|
|
14698
|
+
// Regular proxy request - narrow the type
|
|
14699
|
+
const proxyData = data;
|
|
14700
|
+
try {
|
|
14701
|
+
console.log('[SmartlinksFrame] Proxy request:', proxyData.method, proxyData.path);
|
|
14702
|
+
const path = proxyData.path.startsWith('/') ? proxyData.path.slice(1) : proxyData.path;
|
|
14703
|
+
const cached = cachedDataRef.current;
|
|
14704
|
+
// Check for cached data matches on GET requests
|
|
14705
|
+
if (proxyData.method === 'GET') {
|
|
14706
|
+
// Collection request
|
|
14707
|
+
if (path.includes('/collection/') && cached.collection) {
|
|
14708
|
+
const collectionIdMatch = path.match(/collection\/([^/]+)/);
|
|
14709
|
+
if (collectionIdMatch && collectionIdMatch[1] === collectionId) {
|
|
14710
|
+
response.data = JSON.parse(JSON.stringify(cached.collection));
|
|
14711
|
+
sendResponse(event.source, event.origin, response);
|
|
14712
|
+
return;
|
|
14713
|
+
}
|
|
14714
|
+
}
|
|
14715
|
+
// Product request
|
|
14716
|
+
if (path.includes('/product/') && cached.product && productId) {
|
|
14717
|
+
const productIdMatch = path.match(/product\/([^/]+)/);
|
|
14718
|
+
if (productIdMatch && productIdMatch[1] === productId) {
|
|
14719
|
+
response.data = JSON.parse(JSON.stringify(cached.product));
|
|
14720
|
+
sendResponse(event.source, event.origin, response);
|
|
14721
|
+
return;
|
|
14722
|
+
}
|
|
14723
|
+
}
|
|
14724
|
+
// Proof request
|
|
14725
|
+
if (path.includes('/proof/') && cached.proof && proofId) {
|
|
14726
|
+
const proofIdMatch = path.match(/proof\/([^/]+)/);
|
|
14727
|
+
if (proofIdMatch && proofIdMatch[1] === proofId) {
|
|
14728
|
+
response.data = JSON.parse(JSON.stringify(cached.proof));
|
|
14729
|
+
sendResponse(event.source, event.origin, response);
|
|
14730
|
+
return;
|
|
14731
|
+
}
|
|
14732
|
+
}
|
|
14733
|
+
// Account request
|
|
14734
|
+
if (path.includes('/account') && cached.user) {
|
|
14735
|
+
response.data = JSON.parse(JSON.stringify({
|
|
14736
|
+
...cached.user.accountData,
|
|
14737
|
+
uid: cached.user.uid,
|
|
14738
|
+
email: cached.user.email,
|
|
14739
|
+
displayName: cached.user.displayName,
|
|
14740
|
+
}));
|
|
14741
|
+
sendResponse(event.source, event.origin, response);
|
|
14742
|
+
return;
|
|
14743
|
+
}
|
|
14744
|
+
}
|
|
14745
|
+
// Forward to actual API using SDK's internal request method
|
|
14746
|
+
// Note: The SDK should handle this via proxy mode, but we're explicitly forwarding
|
|
14747
|
+
const apiMethod = proxyData.method.toLowerCase();
|
|
14748
|
+
// Use the SDK's http utilities
|
|
14749
|
+
let result;
|
|
14750
|
+
switch (apiMethod) {
|
|
14751
|
+
case 'get':
|
|
14752
|
+
result = await smartlinks.http?.get?.(path) ??
|
|
14753
|
+
await fetch(`/api/v1/${path}`).then(r => r.json());
|
|
14754
|
+
break;
|
|
14755
|
+
case 'post':
|
|
14756
|
+
result = await smartlinks.http?.post?.(path, proxyData.body) ??
|
|
14757
|
+
await fetch(`/api/v1/${path}`, { method: 'POST', body: JSON.stringify(proxyData.body) }).then(r => r.json());
|
|
14758
|
+
break;
|
|
14759
|
+
case 'put':
|
|
14760
|
+
result = await smartlinks.http?.put?.(path, proxyData.body) ??
|
|
14761
|
+
await fetch(`/api/v1/${path}`, { method: 'PUT', body: JSON.stringify(proxyData.body) }).then(r => r.json());
|
|
14762
|
+
break;
|
|
14763
|
+
case 'patch':
|
|
14764
|
+
result = await smartlinks.http?.patch?.(path, proxyData.body) ??
|
|
14765
|
+
await fetch(`/api/v1/${path}`, { method: 'PATCH', body: JSON.stringify(proxyData.body) }).then(r => r.json());
|
|
14766
|
+
break;
|
|
14767
|
+
case 'delete':
|
|
14768
|
+
result = await smartlinks.http?.delete?.(path) ??
|
|
14769
|
+
await fetch(`/api/v1/${path}`, { method: 'DELETE' }).then(r => r.json());
|
|
14770
|
+
break;
|
|
14771
|
+
}
|
|
14772
|
+
response.data = result;
|
|
14773
|
+
}
|
|
14774
|
+
catch (err) {
|
|
14775
|
+
console.error('[SmartlinksFrame] Proxy error:', err);
|
|
14776
|
+
response.error = err?.message || 'Unknown error';
|
|
14777
|
+
onErrorRef.current?.(err);
|
|
14778
|
+
}
|
|
14779
|
+
sendResponse(event.source, event.origin, response);
|
|
14780
|
+
}, [collectionId, productId, proofId, sendResponse]);
|
|
14781
|
+
// Handle standardized iframe messages (auth, resize, redirect)
|
|
14782
|
+
const handleStandardMessage = useCallback(async (data, event) => {
|
|
14783
|
+
console.log('[SmartlinksFrame] Iframe message:', data.type);
|
|
14784
|
+
switch (data.type) {
|
|
14785
|
+
case 'smartlinks:resize': {
|
|
14786
|
+
// Handled by useIframeResize hook
|
|
14787
|
+
break;
|
|
14788
|
+
}
|
|
14789
|
+
case 'smartlinks:redirect': {
|
|
14790
|
+
const url = data.payload?.url;
|
|
14791
|
+
if (url && typeof url === 'string') {
|
|
14792
|
+
window.location.href = url;
|
|
14793
|
+
}
|
|
14794
|
+
break;
|
|
14795
|
+
}
|
|
14796
|
+
case 'smartlinks:authkit:login': {
|
|
14797
|
+
const { token, user: iframeUser, accountData, messageId } = data.payload || {};
|
|
14798
|
+
// If no login function available (no AuthProvider), just acknowledge
|
|
14799
|
+
if (!loginRef.current) {
|
|
14800
|
+
console.log('[SmartlinksFrame] No AuthProvider - ignoring auth login');
|
|
14801
|
+
sendResponse(event.source, event.origin, {
|
|
14802
|
+
type: 'smartlinks:authkit:login-acknowledged',
|
|
14803
|
+
messageId,
|
|
14804
|
+
success: false,
|
|
14805
|
+
error: 'No AuthProvider available',
|
|
14806
|
+
});
|
|
14807
|
+
break;
|
|
14808
|
+
}
|
|
14809
|
+
try {
|
|
14810
|
+
// Validate token using SDK
|
|
14811
|
+
console.log('[SmartlinksFrame] Validating auth token...');
|
|
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
|
+
};
|
|
14824
|
+
// Use AuthProvider's login to persist session
|
|
14825
|
+
await loginRef.current(token, authUser, accountData);
|
|
14826
|
+
// Send acknowledgment
|
|
14827
|
+
sendResponse(event.source, event.origin, {
|
|
14828
|
+
type: 'smartlinks:authkit:login-acknowledged',
|
|
14829
|
+
messageId,
|
|
14830
|
+
success: true,
|
|
14831
|
+
});
|
|
14832
|
+
console.log('[SmartlinksFrame] Auth login acknowledged');
|
|
14833
|
+
}
|
|
14834
|
+
catch (err) {
|
|
14835
|
+
console.error('[SmartlinksFrame] Auth login failed:', err);
|
|
14836
|
+
sendResponse(event.source, event.origin, {
|
|
14837
|
+
type: 'smartlinks:authkit:login-acknowledged',
|
|
14838
|
+
messageId,
|
|
14839
|
+
success: false,
|
|
14840
|
+
error: err?.message || 'Token validation failed',
|
|
14841
|
+
});
|
|
14842
|
+
onErrorRef.current?.(err);
|
|
14843
|
+
}
|
|
14844
|
+
break;
|
|
14845
|
+
}
|
|
14846
|
+
case 'smartlinks:authkit:logout': {
|
|
14847
|
+
console.log('[SmartlinksFrame] Processing logout from iframe');
|
|
14848
|
+
if (logoutRef.current) {
|
|
14849
|
+
await logoutRef.current();
|
|
14850
|
+
}
|
|
14851
|
+
else {
|
|
14852
|
+
console.log('[SmartlinksFrame] No AuthProvider - ignoring logout');
|
|
14853
|
+
}
|
|
14854
|
+
break;
|
|
14855
|
+
}
|
|
14856
|
+
case 'smartlinks:authkit:redirect': {
|
|
14857
|
+
const url = data.payload?.url;
|
|
14858
|
+
if (url && typeof url === 'string') {
|
|
14859
|
+
window.location.href = url;
|
|
14860
|
+
}
|
|
14861
|
+
break;
|
|
14862
|
+
}
|
|
14863
|
+
}
|
|
14864
|
+
}, [sendResponse]);
|
|
14865
|
+
// Handle chunked file uploads
|
|
14866
|
+
const handleUpload = useCallback(async (data, event) => {
|
|
14867
|
+
const uploads = uploadsRef.current;
|
|
14868
|
+
switch (data.phase) {
|
|
14869
|
+
case 'start': {
|
|
14870
|
+
const startData = data;
|
|
14871
|
+
uploads.set(startData.id, {
|
|
14872
|
+
chunks: [],
|
|
14873
|
+
fields: startData.fields,
|
|
14874
|
+
fileInfo: startData.fileInfo,
|
|
14875
|
+
path: startData.path,
|
|
14876
|
+
});
|
|
14877
|
+
break;
|
|
14878
|
+
}
|
|
14879
|
+
case 'chunk': {
|
|
14880
|
+
const chunkData = data;
|
|
14881
|
+
const upload = uploads.get(chunkData.id);
|
|
14882
|
+
if (upload) {
|
|
14883
|
+
// Convert ArrayBuffer to Uint8Array and store as regular array buffer
|
|
14884
|
+
const uint8Array = new Uint8Array(chunkData.chunk);
|
|
14885
|
+
upload.chunks.push(uint8Array);
|
|
14886
|
+
sendResponse(event.source, event.origin, {
|
|
14887
|
+
_smartlinksProxyUpload: true,
|
|
14888
|
+
phase: 'ack',
|
|
14889
|
+
id: chunkData.id,
|
|
14890
|
+
seq: chunkData.seq,
|
|
14891
|
+
});
|
|
14892
|
+
}
|
|
14893
|
+
break;
|
|
14894
|
+
}
|
|
14895
|
+
case 'end': {
|
|
14896
|
+
const endData = data;
|
|
14897
|
+
const upload = uploads.get(endData.id);
|
|
14898
|
+
if (!upload)
|
|
14899
|
+
break;
|
|
14900
|
+
try {
|
|
14901
|
+
// Reconstruct file from chunks - convert to regular arrays for Blob
|
|
14902
|
+
const blobParts = upload.chunks.map(chunk => chunk.buffer.slice(0));
|
|
14903
|
+
const blob = new Blob(blobParts, {
|
|
14904
|
+
type: upload.fileInfo.type || 'application/octet-stream'
|
|
14905
|
+
});
|
|
14906
|
+
const formData = new FormData();
|
|
14907
|
+
upload.fields.forEach(([key, value]) => formData.append(key, value));
|
|
14908
|
+
formData.append(upload.fileInfo.key || 'file', blob, upload.fileInfo.name || 'upload.bin');
|
|
14909
|
+
// Upload via SDK or direct fetch
|
|
14910
|
+
const path = upload.path.startsWith('/') ? upload.path.slice(1) : upload.path;
|
|
14911
|
+
const baseUrl = smartlinks.getBaseUrl?.() || '/api/v1';
|
|
14912
|
+
const response = await fetch(`${baseUrl}/${path}`, {
|
|
14913
|
+
method: 'POST',
|
|
14914
|
+
body: formData,
|
|
14915
|
+
// Let browser set Content-Type with boundary
|
|
14916
|
+
});
|
|
14917
|
+
if (!response.ok) {
|
|
14918
|
+
throw new Error(`Upload failed: ${response.status}`);
|
|
14919
|
+
}
|
|
14920
|
+
const result = await response.json();
|
|
14921
|
+
sendResponse(event.source, event.origin, {
|
|
14922
|
+
_smartlinksProxyUpload: true,
|
|
14923
|
+
phase: 'done',
|
|
14924
|
+
id: endData.id,
|
|
14925
|
+
ok: true,
|
|
14926
|
+
data: result,
|
|
14927
|
+
});
|
|
14928
|
+
}
|
|
14929
|
+
catch (err) {
|
|
14930
|
+
console.error('[SmartlinksFrame] Upload failed:', err);
|
|
14931
|
+
sendResponse(event.source, event.origin, {
|
|
14932
|
+
_smartlinksProxyUpload: true,
|
|
14933
|
+
phase: 'done',
|
|
14934
|
+
id: endData.id,
|
|
14935
|
+
ok: false,
|
|
14936
|
+
error: err?.message || 'Upload failed',
|
|
14937
|
+
});
|
|
14938
|
+
onErrorRef.current?.(err);
|
|
14939
|
+
}
|
|
14940
|
+
uploads.delete(endData.id);
|
|
14941
|
+
break;
|
|
14942
|
+
}
|
|
14943
|
+
}
|
|
14944
|
+
}, [sendResponse]);
|
|
14945
|
+
// Main message handler
|
|
14946
|
+
useEffect(() => {
|
|
14947
|
+
const handleMessage = async (event) => {
|
|
14948
|
+
// Validate source is our iframe
|
|
14949
|
+
if (!iframeRef.current || event.source !== iframeRef.current.contentWindow) {
|
|
14950
|
+
return;
|
|
14951
|
+
}
|
|
14952
|
+
const data = event.data;
|
|
14953
|
+
if (!data || typeof data !== 'object')
|
|
14954
|
+
return;
|
|
14955
|
+
// Route changes (deep linking)
|
|
14956
|
+
if (data.type === 'smartlinks-route-change') {
|
|
14957
|
+
handleRouteChange(data);
|
|
14958
|
+
return;
|
|
14959
|
+
}
|
|
14960
|
+
// Standardized iframe messages
|
|
14961
|
+
if (data._smartlinksIframeMessage) {
|
|
14962
|
+
await handleStandardMessage(data, event);
|
|
14963
|
+
return;
|
|
14964
|
+
}
|
|
14965
|
+
// File upload proxy
|
|
14966
|
+
if (data._smartlinksProxyUpload) {
|
|
14967
|
+
await handleUpload(data, event);
|
|
14968
|
+
return;
|
|
14969
|
+
}
|
|
14970
|
+
// API proxy requests
|
|
14971
|
+
if (data._smartlinksProxyRequest) {
|
|
14972
|
+
await handleProxyRequest(data, event);
|
|
14973
|
+
return;
|
|
14974
|
+
}
|
|
14975
|
+
};
|
|
14976
|
+
window.addEventListener('message', handleMessage);
|
|
14977
|
+
return () => window.removeEventListener('message', handleMessage);
|
|
14978
|
+
}, [iframeRef, handleRouteChange, handleStandardMessage, handleUpload, handleProxyRequest]);
|
|
14979
|
+
}
|
|
14980
|
+
|
|
14313
14981
|
const ProtectedRoute = ({ children, fallback, redirectTo, }) => {
|
|
14314
14982
|
const { isAuthenticated, isLoading } = useAuth();
|
|
14315
14983
|
// Show loading state
|
|
@@ -14380,5 +15048,5 @@ async function setDefaultAuthKitId(collectionId, authKitId) {
|
|
|
14380
15048
|
});
|
|
14381
15049
|
}
|
|
14382
15050
|
|
|
14383
|
-
export { AccountManagement, AuthProvider, AuthUIPreview, SmartlinksAuthUI as FirebaseAuthUI, ProtectedRoute, SchemaFieldRenderer, SmartlinksAuthUI, getDefaultAuthKitId, getEditableFields, getErrorCode, getErrorStatusCode, getFriendlyErrorMessage, getRegistrationFields, isAuthError, isConflictError, isRateLimitError, isServerError, setDefaultAuthKitId, sortFieldsByPlacement, tokenStorage, useAuth };
|
|
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 };
|
|
14384
15052
|
//# sourceMappingURL=index.esm.js.map
|