@proveanything/smartlinks-auth-ui 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -11447,6 +11447,7 @@ const tokenStorage = {
11447
11447
  },
11448
11448
  };
11449
11449
 
11450
+ // Export context for optional usage (e.g., SmartlinksFrame can work without AuthProvider)
11450
11451
  const AuthContext = React.createContext(undefined);
11451
11452
  const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 * 1000, preloadAccountInfo = false,
11452
11453
  // Token refresh settings
@@ -14330,6 +14331,628 @@ const AccountManagement = ({ apiEndpoint, clientId, collectionId, onError, class
14330
14331
  }, className: "auth-button button-secondary", children: "Cancel" })] })] }))] }))] }));
14331
14332
  };
14332
14333
 
14334
+ // Re-import the constant since we can't import from types
14335
+ const KNOWN_PARAMS = new Set([
14336
+ 'collectionId',
14337
+ 'appId',
14338
+ 'productId',
14339
+ 'proofId',
14340
+ 'isAdmin',
14341
+ 'dark',
14342
+ 'parentUrl',
14343
+ 'theme',
14344
+ 'lang',
14345
+ ]);
14346
+ /**
14347
+ * Hook to handle all iframe postMessage communication.
14348
+ *
14349
+ * Handles:
14350
+ * - Route changes (deep linking)
14351
+ * - API proxy requests
14352
+ * - Auth messages (login/logout)
14353
+ * - File uploads (chunked)
14354
+ * - Redirects
14355
+ *
14356
+ * @param iframeRef - Ref to the iframe element
14357
+ * @param options - Configuration and callbacks
14358
+ */
14359
+ function useIframeMessages(iframeRef, options) {
14360
+ const { collectionId, productId, proofId, cachedData, login, logout, onRouteChange, onError } = options;
14361
+ // Track uploads in progress
14362
+ const uploadsRef = React.useRef(new Map());
14363
+ // Track initial load to skip first route change
14364
+ const isInitialLoadRef = React.useRef(true);
14365
+ // Stable refs to avoid dependency issues
14366
+ const cachedDataRef = React.useRef(cachedData);
14367
+ const onRouteChangeRef = React.useRef(onRouteChange);
14368
+ const onErrorRef = React.useRef(onError);
14369
+ const loginRef = React.useRef(login);
14370
+ const logoutRef = React.useRef(logout);
14371
+ // Keep refs in sync
14372
+ React.useEffect(() => {
14373
+ cachedDataRef.current = cachedData;
14374
+ }, [cachedData]);
14375
+ React.useEffect(() => {
14376
+ onRouteChangeRef.current = onRouteChange;
14377
+ }, [onRouteChange]);
14378
+ React.useEffect(() => {
14379
+ onErrorRef.current = onError;
14380
+ }, [onError]);
14381
+ React.useEffect(() => {
14382
+ loginRef.current = login;
14383
+ }, [login]);
14384
+ React.useEffect(() => {
14385
+ logoutRef.current = logout;
14386
+ }, [logout]);
14387
+ // Send response back to iframe
14388
+ const sendResponse = React.useCallback((source, origin, message) => {
14389
+ if (source && 'postMessage' in source) {
14390
+ source.postMessage(message, origin);
14391
+ }
14392
+ }, []);
14393
+ // Handle route change messages (deep linking)
14394
+ const handleRouteChange = React.useCallback((data) => {
14395
+ // Skip initial load to prevent duplicating path
14396
+ if (isInitialLoadRef.current) {
14397
+ isInitialLoadRef.current = false;
14398
+ return;
14399
+ }
14400
+ const { context = {}, state = {}, path = '' } = data;
14401
+ // Filter out known iframe params, keep only app-specific state
14402
+ const filteredState = {};
14403
+ Object.entries(context).forEach(([key, value]) => {
14404
+ if (value != null && !KNOWN_PARAMS.has(key)) {
14405
+ filteredState[key] = value;
14406
+ }
14407
+ });
14408
+ Object.entries(state).forEach(([key, value]) => {
14409
+ if (value != null && !KNOWN_PARAMS.has(key)) {
14410
+ filteredState[key] = value;
14411
+ }
14412
+ });
14413
+ onRouteChangeRef.current?.(path, filteredState);
14414
+ }, []);
14415
+ // Handle API proxy requests
14416
+ const handleProxyRequest = React.useCallback(async (data, event) => {
14417
+ const response = {
14418
+ _smartlinksProxyResponse: true,
14419
+ id: data.id,
14420
+ };
14421
+ // Handle custom proxy requests (redirects, etc.)
14422
+ if ('_smartlinksCustomProxyRequest' in data && data._smartlinksCustomProxyRequest) {
14423
+ if (data.request === 'REDIRECT') {
14424
+ const url = data.params?.url;
14425
+ if (url) {
14426
+ window.location.href = url;
14427
+ }
14428
+ response.data = { success: true };
14429
+ sendResponse(event.source, event.origin, response);
14430
+ return;
14431
+ }
14432
+ }
14433
+ // Regular proxy request - narrow the type
14434
+ const proxyData = data;
14435
+ try {
14436
+ console.log('[SmartlinksFrame] Proxy request:', proxyData.method, proxyData.path);
14437
+ const path = proxyData.path.startsWith('/') ? proxyData.path.slice(1) : proxyData.path;
14438
+ const cached = cachedDataRef.current;
14439
+ // Check for cached data matches on GET requests
14440
+ if (proxyData.method === 'GET') {
14441
+ // Collection request
14442
+ if (path.includes('/collection/') && cached.collection) {
14443
+ const collectionIdMatch = path.match(/collection\/([^/]+)/);
14444
+ if (collectionIdMatch && collectionIdMatch[1] === collectionId) {
14445
+ response.data = JSON.parse(JSON.stringify(cached.collection));
14446
+ sendResponse(event.source, event.origin, response);
14447
+ return;
14448
+ }
14449
+ }
14450
+ // Product request
14451
+ if (path.includes('/product/') && cached.product && productId) {
14452
+ const productIdMatch = path.match(/product\/([^/]+)/);
14453
+ if (productIdMatch && productIdMatch[1] === productId) {
14454
+ response.data = JSON.parse(JSON.stringify(cached.product));
14455
+ sendResponse(event.source, event.origin, response);
14456
+ return;
14457
+ }
14458
+ }
14459
+ // Proof request
14460
+ if (path.includes('/proof/') && cached.proof && proofId) {
14461
+ const proofIdMatch = path.match(/proof\/([^/]+)/);
14462
+ if (proofIdMatch && proofIdMatch[1] === proofId) {
14463
+ response.data = JSON.parse(JSON.stringify(cached.proof));
14464
+ sendResponse(event.source, event.origin, response);
14465
+ return;
14466
+ }
14467
+ }
14468
+ // Account request
14469
+ if (path.includes('/account') && cached.user) {
14470
+ response.data = JSON.parse(JSON.stringify({
14471
+ ...cached.user.accountData,
14472
+ uid: cached.user.uid,
14473
+ email: cached.user.email,
14474
+ displayName: cached.user.displayName,
14475
+ }));
14476
+ sendResponse(event.source, event.origin, response);
14477
+ return;
14478
+ }
14479
+ }
14480
+ // Forward to actual API using SDK's internal request method
14481
+ // Note: The SDK should handle this via proxy mode, but we're explicitly forwarding
14482
+ const apiMethod = proxyData.method.toLowerCase();
14483
+ // Use the SDK's http utilities
14484
+ let result;
14485
+ switch (apiMethod) {
14486
+ case 'get':
14487
+ result = await smartlinks__namespace.http?.get?.(path) ??
14488
+ await fetch(`/api/v1/${path}`).then(r => r.json());
14489
+ break;
14490
+ case 'post':
14491
+ result = await smartlinks__namespace.http?.post?.(path, proxyData.body) ??
14492
+ await fetch(`/api/v1/${path}`, { method: 'POST', body: JSON.stringify(proxyData.body) }).then(r => r.json());
14493
+ break;
14494
+ case 'put':
14495
+ result = await smartlinks__namespace.http?.put?.(path, proxyData.body) ??
14496
+ await fetch(`/api/v1/${path}`, { method: 'PUT', body: JSON.stringify(proxyData.body) }).then(r => r.json());
14497
+ break;
14498
+ case 'patch':
14499
+ result = await smartlinks__namespace.http?.patch?.(path, proxyData.body) ??
14500
+ await fetch(`/api/v1/${path}`, { method: 'PATCH', body: JSON.stringify(proxyData.body) }).then(r => r.json());
14501
+ break;
14502
+ case 'delete':
14503
+ result = await smartlinks__namespace.http?.delete?.(path) ??
14504
+ await fetch(`/api/v1/${path}`, { method: 'DELETE' }).then(r => r.json());
14505
+ break;
14506
+ }
14507
+ response.data = result;
14508
+ }
14509
+ catch (err) {
14510
+ console.error('[SmartlinksFrame] Proxy error:', err);
14511
+ response.error = err?.message || 'Unknown error';
14512
+ onErrorRef.current?.(err);
14513
+ }
14514
+ sendResponse(event.source, event.origin, response);
14515
+ }, [collectionId, productId, proofId, sendResponse]);
14516
+ // Handle standardized iframe messages (auth, resize, redirect)
14517
+ const handleStandardMessage = React.useCallback(async (data, event) => {
14518
+ console.log('[SmartlinksFrame] Iframe message:', data.type);
14519
+ switch (data.type) {
14520
+ case 'smartlinks:resize': {
14521
+ // Handled by useIframeResize hook
14522
+ break;
14523
+ }
14524
+ case 'smartlinks:redirect': {
14525
+ const url = data.payload?.url;
14526
+ if (url && typeof url === 'string') {
14527
+ window.location.href = url;
14528
+ }
14529
+ break;
14530
+ }
14531
+ case 'smartlinks:authkit:login': {
14532
+ const { token, user: iframeUser, accountData, messageId } = data.payload || {};
14533
+ // If no login function available (no AuthProvider), just acknowledge
14534
+ if (!loginRef.current) {
14535
+ console.log('[SmartlinksFrame] No AuthProvider - ignoring auth login');
14536
+ sendResponse(event.source, event.origin, {
14537
+ type: 'smartlinks:authkit:login-acknowledged',
14538
+ messageId,
14539
+ success: false,
14540
+ error: 'No AuthProvider available',
14541
+ });
14542
+ break;
14543
+ }
14544
+ try {
14545
+ // Validate token using SDK
14546
+ console.log('[SmartlinksFrame] Validating auth token...');
14547
+ await smartlinks__namespace.auth.verifyToken(token);
14548
+ // Use AuthProvider's login to persist session
14549
+ await loginRef.current(token, iframeUser, accountData);
14550
+ // Send acknowledgment
14551
+ sendResponse(event.source, event.origin, {
14552
+ type: 'smartlinks:authkit:login-acknowledged',
14553
+ messageId,
14554
+ success: true,
14555
+ });
14556
+ console.log('[SmartlinksFrame] Auth login acknowledged');
14557
+ }
14558
+ catch (err) {
14559
+ console.error('[SmartlinksFrame] Auth login failed:', err);
14560
+ sendResponse(event.source, event.origin, {
14561
+ type: 'smartlinks:authkit:login-acknowledged',
14562
+ messageId,
14563
+ success: false,
14564
+ error: err?.message || 'Token validation failed',
14565
+ });
14566
+ onErrorRef.current?.(err);
14567
+ }
14568
+ break;
14569
+ }
14570
+ case 'smartlinks:authkit:logout': {
14571
+ console.log('[SmartlinksFrame] Processing logout from iframe');
14572
+ if (logoutRef.current) {
14573
+ await logoutRef.current();
14574
+ }
14575
+ else {
14576
+ console.log('[SmartlinksFrame] No AuthProvider - ignoring logout');
14577
+ }
14578
+ break;
14579
+ }
14580
+ case 'smartlinks:authkit:redirect': {
14581
+ const url = data.payload?.url;
14582
+ if (url && typeof url === 'string') {
14583
+ window.location.href = url;
14584
+ }
14585
+ break;
14586
+ }
14587
+ }
14588
+ }, [sendResponse]);
14589
+ // Handle chunked file uploads
14590
+ const handleUpload = React.useCallback(async (data, event) => {
14591
+ const uploads = uploadsRef.current;
14592
+ switch (data.phase) {
14593
+ case 'start': {
14594
+ const startData = data;
14595
+ uploads.set(startData.id, {
14596
+ chunks: [],
14597
+ fields: startData.fields,
14598
+ fileInfo: startData.fileInfo,
14599
+ path: startData.path,
14600
+ });
14601
+ break;
14602
+ }
14603
+ case 'chunk': {
14604
+ const chunkData = data;
14605
+ const upload = uploads.get(chunkData.id);
14606
+ if (upload) {
14607
+ // Convert ArrayBuffer to Uint8Array and store as regular array buffer
14608
+ const uint8Array = new Uint8Array(chunkData.chunk);
14609
+ upload.chunks.push(uint8Array);
14610
+ sendResponse(event.source, event.origin, {
14611
+ _smartlinksProxyUpload: true,
14612
+ phase: 'ack',
14613
+ id: chunkData.id,
14614
+ seq: chunkData.seq,
14615
+ });
14616
+ }
14617
+ break;
14618
+ }
14619
+ case 'end': {
14620
+ const endData = data;
14621
+ const upload = uploads.get(endData.id);
14622
+ if (!upload)
14623
+ break;
14624
+ try {
14625
+ // Reconstruct file from chunks - convert to regular arrays for Blob
14626
+ const blobParts = upload.chunks.map(chunk => chunk.buffer.slice(0));
14627
+ const blob = new Blob(blobParts, {
14628
+ type: upload.fileInfo.type || 'application/octet-stream'
14629
+ });
14630
+ const formData = new FormData();
14631
+ upload.fields.forEach(([key, value]) => formData.append(key, value));
14632
+ formData.append(upload.fileInfo.key || 'file', blob, upload.fileInfo.name || 'upload.bin');
14633
+ // Upload via SDK or direct fetch
14634
+ const path = upload.path.startsWith('/') ? upload.path.slice(1) : upload.path;
14635
+ const baseUrl = smartlinks__namespace.getBaseUrl?.() || '/api/v1';
14636
+ const response = await fetch(`${baseUrl}/${path}`, {
14637
+ method: 'POST',
14638
+ body: formData,
14639
+ // Let browser set Content-Type with boundary
14640
+ });
14641
+ if (!response.ok) {
14642
+ throw new Error(`Upload failed: ${response.status}`);
14643
+ }
14644
+ const result = await response.json();
14645
+ sendResponse(event.source, event.origin, {
14646
+ _smartlinksProxyUpload: true,
14647
+ phase: 'done',
14648
+ id: endData.id,
14649
+ ok: true,
14650
+ data: result,
14651
+ });
14652
+ }
14653
+ catch (err) {
14654
+ console.error('[SmartlinksFrame] Upload failed:', err);
14655
+ sendResponse(event.source, event.origin, {
14656
+ _smartlinksProxyUpload: true,
14657
+ phase: 'done',
14658
+ id: endData.id,
14659
+ ok: false,
14660
+ error: err?.message || 'Upload failed',
14661
+ });
14662
+ onErrorRef.current?.(err);
14663
+ }
14664
+ uploads.delete(endData.id);
14665
+ break;
14666
+ }
14667
+ }
14668
+ }, [sendResponse]);
14669
+ // Main message handler
14670
+ React.useEffect(() => {
14671
+ const handleMessage = async (event) => {
14672
+ // Validate source is our iframe
14673
+ if (!iframeRef.current || event.source !== iframeRef.current.contentWindow) {
14674
+ return;
14675
+ }
14676
+ const data = event.data;
14677
+ if (!data || typeof data !== 'object')
14678
+ return;
14679
+ // Route changes (deep linking)
14680
+ if (data.type === 'smartlinks-route-change') {
14681
+ handleRouteChange(data);
14682
+ return;
14683
+ }
14684
+ // Standardized iframe messages
14685
+ if (data._smartlinksIframeMessage) {
14686
+ await handleStandardMessage(data, event);
14687
+ return;
14688
+ }
14689
+ // File upload proxy
14690
+ if (data._smartlinksProxyUpload) {
14691
+ await handleUpload(data, event);
14692
+ return;
14693
+ }
14694
+ // API proxy requests
14695
+ if (data._smartlinksProxyRequest) {
14696
+ await handleProxyRequest(data, event);
14697
+ return;
14698
+ }
14699
+ };
14700
+ window.addEventListener('message', handleMessage);
14701
+ return () => window.removeEventListener('message', handleMessage);
14702
+ }, [iframeRef, handleRouteChange, handleStandardMessage, handleUpload, handleProxyRequest]);
14703
+ }
14704
+
14705
+ /**
14706
+ * Hook to handle iframe height management.
14707
+ *
14708
+ * Supports two modes:
14709
+ * 1. Viewport-based: Calculates height based on iframe position and viewport
14710
+ * 2. Content-based: Listens for smartlinks:resize messages from the iframe
14711
+ *
14712
+ * @param iframeRef - Ref to the iframe element
14713
+ * @param options - Resize configuration options
14714
+ * @returns Current calculated height in pixels
14715
+ */
14716
+ function useIframeResize(iframeRef, options = {}) {
14717
+ const { autoResize = true, minHeight, maxHeight } = options;
14718
+ const [height, setHeight] = React.useState(0);
14719
+ // Calculate height based on viewport position
14720
+ const calculateHeight = React.useCallback(() => {
14721
+ const iframe = iframeRef.current;
14722
+ if (!iframe)
14723
+ return;
14724
+ const container = iframe.parentElement;
14725
+ if (!container)
14726
+ return;
14727
+ const rect = container.getBoundingClientRect();
14728
+ const viewportHeight = window.innerHeight;
14729
+ let calculatedHeight = Math.max(0, viewportHeight - rect.top);
14730
+ // Apply constraints
14731
+ if (minHeight !== undefined) {
14732
+ calculatedHeight = Math.max(calculatedHeight, minHeight);
14733
+ }
14734
+ if (maxHeight !== undefined) {
14735
+ calculatedHeight = Math.min(calculatedHeight, maxHeight);
14736
+ }
14737
+ setHeight(calculatedHeight);
14738
+ }, [minHeight, maxHeight, iframeRef]);
14739
+ // Handle viewport resize events
14740
+ React.useEffect(() => {
14741
+ if (!autoResize)
14742
+ return;
14743
+ // Initial calculation with multiple retries for late layout
14744
+ calculateHeight();
14745
+ const t1 = setTimeout(calculateHeight, 10);
14746
+ const t2 = setTimeout(calculateHeight, 100);
14747
+ const t3 = setTimeout(calculateHeight, 500);
14748
+ const handleResize = () => calculateHeight();
14749
+ window.addEventListener('resize', handleResize);
14750
+ window.addEventListener('orientationchange', handleResize);
14751
+ return () => {
14752
+ clearTimeout(t1);
14753
+ clearTimeout(t2);
14754
+ clearTimeout(t3);
14755
+ window.removeEventListener('resize', handleResize);
14756
+ window.removeEventListener('orientationchange', handleResize);
14757
+ };
14758
+ }, [autoResize, calculateHeight]);
14759
+ // Listen for content-based resize messages from iframe
14760
+ React.useEffect(() => {
14761
+ const handleMessage = (event) => {
14762
+ // Validate source is our iframe
14763
+ if (iframeRef.current && event.source !== iframeRef.current.contentWindow) {
14764
+ return;
14765
+ }
14766
+ const data = event.data;
14767
+ // Handle smartlinks:resize message
14768
+ if (data?._smartlinksIframeMessage && data?.type === 'smartlinks:resize') {
14769
+ const contentHeight = data.payload?.height;
14770
+ if (typeof contentHeight === 'number' && contentHeight > 0) {
14771
+ let newHeight = contentHeight;
14772
+ // Apply constraints
14773
+ if (minHeight !== undefined) {
14774
+ newHeight = Math.max(newHeight, minHeight);
14775
+ }
14776
+ if (maxHeight !== undefined) {
14777
+ newHeight = Math.min(newHeight, maxHeight);
14778
+ }
14779
+ setHeight(newHeight);
14780
+ }
14781
+ }
14782
+ };
14783
+ window.addEventListener('message', handleMessage);
14784
+ return () => window.removeEventListener('message', handleMessage);
14785
+ }, [iframeRef, minHeight, maxHeight]);
14786
+ return height;
14787
+ }
14788
+
14789
+ /**
14790
+ * Hook to detect if the current user is an admin of the collection or proof.
14791
+ *
14792
+ * Admin status is determined by checking if the user's UID exists in the
14793
+ * `roles` object of either the collection or proof.
14794
+ *
14795
+ * @param collection - Collection object with optional roles
14796
+ * @param proof - Proof object with optional roles
14797
+ * @param user - Current authenticated user
14798
+ * @returns boolean indicating admin status
14799
+ */
14800
+ function useAdminDetection(collection, proof, user) {
14801
+ return React.useMemo(() => {
14802
+ if (!user?.uid)
14803
+ return false;
14804
+ // Check collection roles
14805
+ if (collection?.roles && collection.roles[user.uid]) {
14806
+ return true;
14807
+ }
14808
+ // Check proof roles (for proof-level admin)
14809
+ if (proof?.roles && proof.roles[user.uid]) {
14810
+ return true;
14811
+ }
14812
+ return false;
14813
+ }, [collection?.roles, proof?.roles, user?.uid]);
14814
+ }
14815
+
14816
+ /**
14817
+ * SmartlinksFrame - Parent-side iframe wrapper for embedding SmartLinks microapps.
14818
+ *
14819
+ * This component handles all bidirectional communication with the embedded iframe:
14820
+ * - API proxy requests (with optional caching)
14821
+ * - Authentication state synchronization (when inside AuthProvider)
14822
+ * - Deep linking / route changes
14823
+ * - Resize management
14824
+ * - File upload proxying
14825
+ *
14826
+ * Can be used with or without AuthProvider - authentication is optional.
14827
+ *
14828
+ * @example
14829
+ * ```tsx
14830
+ * // With authentication
14831
+ * <AuthProvider collectionId="my-collection">
14832
+ * <SmartlinksFrame
14833
+ * collectionId="my-collection"
14834
+ * appId="warranty-app"
14835
+ * appUrl="https://warranty.lovable.app"
14836
+ * collection={collectionData}
14837
+ * />
14838
+ * </AuthProvider>
14839
+ *
14840
+ * // Without authentication (public/anonymous access)
14841
+ * <SmartlinksFrame
14842
+ * collectionId="my-collection"
14843
+ * appId="info-app"
14844
+ * appUrl="https://info-app.lovable.app"
14845
+ * />
14846
+ * ```
14847
+ */
14848
+ const SmartlinksFrame = ({ collectionId, appId, productId, proofId, appUrl, version = 'stable', collection, product, proof, initialPath, onRouteChange, autoResize = true, minHeight, maxHeight, onReady, onError, className, style, }) => {
14849
+ const iframeRef = React.useRef(null);
14850
+ // Get auth context if available (optional - SmartlinksFrame works without AuthProvider)
14851
+ const authContext = React.useContext(AuthContext);
14852
+ const user = authContext?.user ?? null;
14853
+ const login = authContext?.login;
14854
+ const logout = authContext?.logout;
14855
+ // Compute isAdmin from collection/proof roles
14856
+ const isAdmin = useAdminDetection(collection, proof, user);
14857
+ // Handle resize
14858
+ const height = useIframeResize(iframeRef, { autoResize, minHeight, maxHeight });
14859
+ // Handle all iframe messages
14860
+ useIframeMessages(iframeRef, {
14861
+ collectionId,
14862
+ productId,
14863
+ proofId,
14864
+ cachedData: { collection, product, proof, user },
14865
+ login,
14866
+ logout,
14867
+ onRouteChange,
14868
+ onError,
14869
+ });
14870
+ // Build iframe URL with all context params
14871
+ const frameSrc = React.useMemo(() => {
14872
+ const params = new URLSearchParams();
14873
+ // Required context
14874
+ params.set('collectionId', collectionId);
14875
+ params.set('appId', appId);
14876
+ // Optional context
14877
+ if (productId)
14878
+ params.set('productId', productId);
14879
+ if (proofId)
14880
+ params.set('proofId', proofId);
14881
+ if (isAdmin)
14882
+ params.set('isAdmin', 'true');
14883
+ // Dark mode from collection
14884
+ const isDark = collection?.dark ?? false;
14885
+ params.set('dark', isDark ? '1' : '0');
14886
+ // Include parent URL for downstream redirects/context
14887
+ try {
14888
+ params.set('parentUrl', window.location.href);
14889
+ }
14890
+ catch {
14891
+ // Ignore if can't access location
14892
+ }
14893
+ // Encode theme from collection if available
14894
+ if (collection) {
14895
+ try {
14896
+ const themeData = {
14897
+ p: collection.primaryColor,
14898
+ s: collection.secondaryColor,
14899
+ m: collection.dark ? 'd' : 'l',
14900
+ };
14901
+ // Only include if we have meaningful data
14902
+ if (themeData.p || themeData.s) {
14903
+ params.set('theme', btoa(JSON.stringify(themeData)));
14904
+ }
14905
+ }
14906
+ catch {
14907
+ // Ignore encoding errors
14908
+ }
14909
+ }
14910
+ // Determine base URL
14911
+ let base = appUrl;
14912
+ // If appUrl doesn't look like a full URL, construct from smartlinks.app
14913
+ if (!appUrl.startsWith('http')) {
14914
+ base = `https://smartlinks.app/apps/${appId}/${version}`;
14915
+ }
14916
+ // Clean up base URL
14917
+ base = base.replace(/#\/?$/, ''); // Remove trailing hash
14918
+ if (base.endsWith('/')) {
14919
+ base = base.slice(0, -1);
14920
+ }
14921
+ // Build hash path
14922
+ let hashPath = initialPath || '';
14923
+ if (hashPath && !hashPath.startsWith('/')) {
14924
+ hashPath = '/' + hashPath;
14925
+ }
14926
+ if (hashPath === '/') {
14927
+ hashPath = '';
14928
+ }
14929
+ // Construct final URL: base#/path?params
14930
+ return `${base}/#/${hashPath}?${params.toString()}`.replace('/#//', '/#/');
14931
+ }, [
14932
+ collectionId,
14933
+ appId,
14934
+ productId,
14935
+ proofId,
14936
+ appUrl,
14937
+ version,
14938
+ initialPath,
14939
+ isAdmin,
14940
+ collection,
14941
+ ]);
14942
+ // Calculate container style
14943
+ const containerStyle = {
14944
+ height: autoResize && height > 0 ? `${height}px` : '100%',
14945
+ width: '100%',
14946
+ ...style,
14947
+ };
14948
+ return (jsxRuntime.jsx("div", { className: className, style: containerStyle, children: jsxRuntime.jsx("iframe", { ref: iframeRef, src: frameSrc, frameBorder: 0, width: "100%", height: "100%", allow: "camera;microphone;fullscreen;geolocation;identity-credentials-get", onLoad: onReady, style: {
14949
+ display: 'block',
14950
+ width: '100%',
14951
+ height: '100%',
14952
+ border: 0,
14953
+ } }) }));
14954
+ };
14955
+
14333
14956
  const ProtectedRoute = ({ children, fallback, redirectTo, }) => {
14334
14957
  const { isAuthenticated, isLoading } = useAuth();
14335
14958
  // Show loading state
@@ -14407,6 +15030,7 @@ exports.FirebaseAuthUI = SmartlinksAuthUI;
14407
15030
  exports.ProtectedRoute = ProtectedRoute;
14408
15031
  exports.SchemaFieldRenderer = SchemaFieldRenderer;
14409
15032
  exports.SmartlinksAuthUI = SmartlinksAuthUI;
15033
+ exports.SmartlinksFrame = SmartlinksFrame;
14410
15034
  exports.getDefaultAuthKitId = getDefaultAuthKitId;
14411
15035
  exports.getEditableFields = getEditableFields;
14412
15036
  exports.getErrorCode = getErrorCode;
@@ -14420,5 +15044,8 @@ exports.isServerError = isServerError;
14420
15044
  exports.setDefaultAuthKitId = setDefaultAuthKitId;
14421
15045
  exports.sortFieldsByPlacement = sortFieldsByPlacement;
14422
15046
  exports.tokenStorage = tokenStorage;
15047
+ exports.useAdminDetection = useAdminDetection;
14423
15048
  exports.useAuth = useAuth;
15049
+ exports.useIframeMessages = useIframeMessages;
15050
+ exports.useIframeResize = useIframeResize;
14424
15051
  //# sourceMappingURL=index.js.map