@proveanything/smartlinks-auth-ui 0.5.21 → 0.6.0
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/ACCOUNT_CACHING.md +349 -0
- package/ANDROID_NATIVE_BRIDGE.md +775 -0
- package/AUTH_STATE_MANAGEMENT.md +262 -0
- package/CAPACITOR_INTEGRATION.md +244 -0
- package/CUSTOMIZATION_GUIDE.md +411 -0
- package/README.md +73 -13
- package/SDK_DEBUGGING_GUIDE.md +217 -0
- package/SMARTLINKS_FRAME.md +302 -0
- package/SMARTLINKS_INTEGRATION.md +330 -0
- package/WHATSAPP_OTP_PLAN.md +106 -0
- package/dist/api.d.ts +15 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/components/ProviderButtons.d.ts +1 -0
- package/dist/components/ProviderButtons.d.ts.map +1 -1
- package/dist/components/SmartlinksAuthUI.d.ts.map +1 -1
- package/dist/context/AuthContext.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +386 -34
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +386 -33
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +98 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/persistentStorage.d.ts +14 -0
- package/dist/utils/persistentStorage.d.ts.map +1 -1
- package/dist/utils/tokenStorage.d.ts +7 -0
- package/dist/utils/tokenStorage.d.ts.map +1 -1
- package/package.json +15 -6
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# IframeResponder Debugging Guide
|
|
2
|
+
|
|
3
|
+
## Issue Summary
|
|
4
|
+
|
|
5
|
+
The `SmartlinksFrame` React component calls `IframeResponder.attach(iframe)` but:
|
|
6
|
+
- The promise never resolves (or silently fails)
|
|
7
|
+
- No `src` URL is returned to set on the iframe
|
|
8
|
+
- The iframe remains empty with no content
|
|
9
|
+
|
|
10
|
+
## Logs Needed
|
|
11
|
+
|
|
12
|
+
Please add `console.log` statements at these key points in `iframeResponder.ts`:
|
|
13
|
+
|
|
14
|
+
### 1. Constructor Initialization
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
constructor(options: IframeResponderOptions) {
|
|
18
|
+
console.log('[IframeResponder] Constructor called', {
|
|
19
|
+
collectionId: options.collectionId,
|
|
20
|
+
appId: options.appId,
|
|
21
|
+
productId: options.productId,
|
|
22
|
+
hasCache: !!options.cache,
|
|
23
|
+
hasCachedApps: !!options.cache?.apps,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
this.options = options;
|
|
27
|
+
this.cache = options.cache;
|
|
28
|
+
this.resolveReady = null;
|
|
29
|
+
this.ready = new Promise((resolve) => {
|
|
30
|
+
this.resolveReady = resolve;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// After resolveAppUrl() is called:
|
|
34
|
+
this.resolveAppUrl()
|
|
35
|
+
.then(() => {
|
|
36
|
+
console.log('[IframeResponder] App URL resolved successfully:', this.appUrl);
|
|
37
|
+
this.resolveReady?.();
|
|
38
|
+
})
|
|
39
|
+
.catch((err) => {
|
|
40
|
+
console.error('[IframeResponder] App URL resolution failed:', err);
|
|
41
|
+
// Make sure this error is propagated!
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 2. resolveAppUrl Method
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
private async resolveAppUrl(): Promise<void> {
|
|
50
|
+
console.log('[IframeResponder] resolveAppUrl started');
|
|
51
|
+
|
|
52
|
+
// Check cache first
|
|
53
|
+
const cachedApps = this.cache.apps;
|
|
54
|
+
if (cachedApps) {
|
|
55
|
+
console.log('[IframeResponder] Found cached apps:', cachedApps.length);
|
|
56
|
+
const app = cachedApps.find(a => a.id === this.options.appId);
|
|
57
|
+
if (app) {
|
|
58
|
+
this.appUrl = this.getVersionUrl(app);
|
|
59
|
+
console.log('[IframeResponder] Using cached app URL:', this.appUrl);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
console.log('[IframeResponder] App not found in cache, fetching from API');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Fetch from API
|
|
66
|
+
try {
|
|
67
|
+
console.log('[IframeResponder] Calling cache.getOrFetch for apps');
|
|
68
|
+
const apps = await cache.getOrFetch<CollectionApp[]>(
|
|
69
|
+
`apps:${this.options.collectionId}`,
|
|
70
|
+
() => this.fetchApps(),
|
|
71
|
+
{ ttl: 5 * 60 * 1000, storage: 'session' }
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
console.log('[IframeResponder] Got apps from API:', apps?.length, apps);
|
|
75
|
+
|
|
76
|
+
const app = apps.find(a => a.id === this.options.appId);
|
|
77
|
+
if (!app) {
|
|
78
|
+
console.error('[IframeResponder] App not found:', this.options.appId, 'Available:', apps.map(a => a.id));
|
|
79
|
+
throw new Error(`App "${this.options.appId}" not found in collection`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
this.appUrl = this.getVersionUrl(app);
|
|
83
|
+
console.log('[IframeResponder] Resolved app URL:', this.appUrl);
|
|
84
|
+
} catch (err) {
|
|
85
|
+
console.error('[IframeResponder] resolveAppUrl error:', err);
|
|
86
|
+
this.options.onError?.(err);
|
|
87
|
+
throw err;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 3. fetchApps Method
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
private async fetchApps(): Promise<CollectionApp[]> {
|
|
96
|
+
const url = `/api/v1/public/collection/${this.options.collectionId}/apps`;
|
|
97
|
+
console.log('[IframeResponder] Fetching apps from:', url);
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const response = await fetch(url);
|
|
101
|
+
console.log('[IframeResponder] Fetch response status:', response.status);
|
|
102
|
+
|
|
103
|
+
if (!response.ok) {
|
|
104
|
+
const text = await response.text();
|
|
105
|
+
console.error('[IframeResponder] Fetch failed:', response.status, text);
|
|
106
|
+
throw new Error(`Failed to fetch apps: ${response.status}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const data = await response.json();
|
|
110
|
+
console.log('[IframeResponder] Fetch response data:', data);
|
|
111
|
+
return data.items || data;
|
|
112
|
+
} catch (err) {
|
|
113
|
+
console.error('[IframeResponder] fetchApps exception:', err);
|
|
114
|
+
throw err;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 4. attach Method
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
async attach(iframe: HTMLIFrameElement): Promise<string> {
|
|
123
|
+
console.log('[IframeResponder] attach() called, waiting for ready...');
|
|
124
|
+
|
|
125
|
+
await this.ready;
|
|
126
|
+
|
|
127
|
+
console.log('[IframeResponder] Ready resolved, appUrl:', this.appUrl);
|
|
128
|
+
|
|
129
|
+
this.iframe = iframe;
|
|
130
|
+
|
|
131
|
+
// Set up listeners...
|
|
132
|
+
|
|
133
|
+
const src = this.buildIframeSrc();
|
|
134
|
+
console.log('[IframeResponder] Built iframe src:', src);
|
|
135
|
+
|
|
136
|
+
return src;
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### 5. buildIframeSrc Method
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
private buildIframeSrc(): string {
|
|
144
|
+
console.log('[IframeResponder] buildIframeSrc called, appUrl:', this.appUrl);
|
|
145
|
+
|
|
146
|
+
if (!this.appUrl) {
|
|
147
|
+
console.error('[IframeResponder] Cannot build src - appUrl is null!');
|
|
148
|
+
throw new Error('App URL not resolved');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const { collectionId, appId, productId } = this.options;
|
|
152
|
+
const { origin: base, hashPath } = new URL(this.appUrl);
|
|
153
|
+
|
|
154
|
+
const params = new URLSearchParams({
|
|
155
|
+
collectionId,
|
|
156
|
+
appId,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
if (productId) {
|
|
160
|
+
params.append('productId', productId);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const finalUrl = `${base}/#/${hashPath}?${params.toString()}`;
|
|
164
|
+
console.log('[IframeResponder] Final iframe URL:', finalUrl);
|
|
165
|
+
return finalUrl;
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## What We Need to See
|
|
170
|
+
|
|
171
|
+
1. **Does the constructor complete?** - "Constructor called" log
|
|
172
|
+
2. **Does URL resolution start?** - "resolveAppUrl started" log
|
|
173
|
+
3. **Is the API called?** - "Fetching apps from" log
|
|
174
|
+
4. **Does the API respond?** - "Fetch response status" log
|
|
175
|
+
5. **Is the app found?** - "Resolved app URL" log
|
|
176
|
+
6. **Does attach wait properly?** - "attach() called" and "Ready resolved" logs
|
|
177
|
+
7. **Is the src built?** - "Built iframe src" log
|
|
178
|
+
|
|
179
|
+
## Likely Failure Points
|
|
180
|
+
|
|
181
|
+
Based on symptoms (no logs after "Initializing"), the issue is probably:
|
|
182
|
+
|
|
183
|
+
1. **`this.ready` promise never resolves** - `resolveAppUrl()` fails silently or hangs
|
|
184
|
+
2. **API fetch hangs** - CORS issue, wrong URL, or network timeout
|
|
185
|
+
3. **cache.getOrFetch hangs** - Storage access issue or internal error
|
|
186
|
+
4. **Error swallowed** - An error occurs but isn't logged/propagated
|
|
187
|
+
|
|
188
|
+
## Quick Test
|
|
189
|
+
|
|
190
|
+
Add this to verify the SDK is working at all:
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
// In IframeResponder constructor
|
|
194
|
+
console.log('[IframeResponder] SDK version check:', {
|
|
195
|
+
hasCache: typeof cache !== 'undefined',
|
|
196
|
+
hasCacheGetOrFetch: typeof cache?.getOrFetch === 'function',
|
|
197
|
+
});
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Expected Console Output (Happy Path)
|
|
201
|
+
|
|
202
|
+
```
|
|
203
|
+
[IframeResponder] Constructor called { collectionId: "power-pop", appId: "dynamicPage" }
|
|
204
|
+
[IframeResponder] resolveAppUrl started
|
|
205
|
+
[IframeResponder] Calling cache.getOrFetch for apps
|
|
206
|
+
[IframeResponder] Fetching apps from: /api/v1/public/collection/power-pop/apps
|
|
207
|
+
[IframeResponder] Fetch response status: 200
|
|
208
|
+
[IframeResponder] Fetch response data: { items: [...] }
|
|
209
|
+
[IframeResponder] Got apps from API: 5
|
|
210
|
+
[IframeResponder] Resolved app URL: https://my-app.lovable.app
|
|
211
|
+
[IframeResponder] App URL resolved successfully: https://my-app.lovable.app
|
|
212
|
+
[IframeResponder] attach() called, waiting for ready...
|
|
213
|
+
[IframeResponder] Ready resolved, appUrl: https://my-app.lovable.app
|
|
214
|
+
[IframeResponder] Built iframe src: https://my-app.lovable.app/#/?collectionId=power-pop&appId=dynamicPage
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
If any of these logs are missing, that tells us where it's failing.
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
# SmartlinksFrame Component
|
|
2
|
+
|
|
3
|
+
React component for embedding SmartLinks microapps with automatic URL resolution, API proxying, authentication sync, and deep linking.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
The component is part of the auth-module and requires `@proveanything/smartlinks` SDK v1.4+:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @proveanything/smartlinks
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { SmartlinksFrame } from '@smartlinks/auth-module';
|
|
17
|
+
|
|
18
|
+
// URL is auto-resolved from collection.getAppsConfig()
|
|
19
|
+
<SmartlinksFrame
|
|
20
|
+
collectionId="acme-wines"
|
|
21
|
+
appId="warranty-registration"
|
|
22
|
+
productId="wine-bottle-123"
|
|
23
|
+
/>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
- **Automatic URL Resolution** - Fetches app config from collection and resolves the correct iframe URL
|
|
29
|
+
- **API Request Proxying** - Forwards API requests from iframe to parent's authenticated session
|
|
30
|
+
- **Smart Caching** - Reduces API calls by serving cached collection, product, and proof data
|
|
31
|
+
- **Authentication Sync** - Handles login/logout events between parent and iframe via AuthProvider
|
|
32
|
+
- **Deep Linking** - Synchronizes iframe routes with parent URL state
|
|
33
|
+
- **Responsive Sizing** - Automatically adjusts iframe height to content
|
|
34
|
+
|
|
35
|
+
## Props
|
|
36
|
+
|
|
37
|
+
| Prop | Type | Required | Description |
|
|
38
|
+
|------|------|----------|-------------|
|
|
39
|
+
| `collectionId` | `string` | ✅ | Collection context |
|
|
40
|
+
| `appId` | `string` | ✅ | App to load (e.g., 'warranty-registration') |
|
|
41
|
+
| `productId` | `string` | | Product context |
|
|
42
|
+
| `proofId` | `string` | | Proof context |
|
|
43
|
+
| `appUrl` | `string` | | Override URL (for local development) |
|
|
44
|
+
| `version` | `'stable' \| 'development'` | | App version (default: 'stable') |
|
|
45
|
+
| `collection` | `object` | | Pre-cached collection data |
|
|
46
|
+
| `product` | `object` | | Pre-cached product data |
|
|
47
|
+
| `proof` | `object` | | Pre-cached proof data |
|
|
48
|
+
| `initialPath` | `string` | | Initial hash path (e.g., '/settings') |
|
|
49
|
+
| `onRouteChange` | `(path, state) => void` | | Called when iframe route changes |
|
|
50
|
+
| `autoResize` | `boolean` | | Auto-adjust height (default: true) |
|
|
51
|
+
| `minHeight` | `number` | | Minimum height in pixels |
|
|
52
|
+
| `maxHeight` | `number` | | Maximum height in pixels |
|
|
53
|
+
| `onReady` | `() => void` | | Called when iframe loads |
|
|
54
|
+
| `onError` | `(error) => void` | | Called on errors |
|
|
55
|
+
| `className` | `string` | | Container CSS class |
|
|
56
|
+
| `style` | `CSSProperties` | | Container inline styles |
|
|
57
|
+
|
|
58
|
+
## Usage Examples
|
|
59
|
+
|
|
60
|
+
### Basic Embedding
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
// App URL resolved automatically from collection apps config
|
|
64
|
+
<SmartlinksFrame
|
|
65
|
+
collectionId="acme-wines"
|
|
66
|
+
appId="warranty"
|
|
67
|
+
productId="cabernet-2023"
|
|
68
|
+
/>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### With Pre-fetched Data (Faster Loading)
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
import { useQuery } from '@tanstack/react-query';
|
|
75
|
+
import * as smartlinks from '@proveanything/smartlinks';
|
|
76
|
+
|
|
77
|
+
const { data: collection } = useQuery(
|
|
78
|
+
['collection', collectionId],
|
|
79
|
+
() => smartlinks.collection.get(collectionId)
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const { data: product } = useQuery(
|
|
83
|
+
['product', productId],
|
|
84
|
+
() => smartlinks.product.get(productId)
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
<SmartlinksFrame
|
|
88
|
+
collectionId={collectionId}
|
|
89
|
+
appId="warranty"
|
|
90
|
+
productId={productId}
|
|
91
|
+
collection={collection}
|
|
92
|
+
product={product}
|
|
93
|
+
/>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### With AuthProvider (Recommended)
|
|
97
|
+
|
|
98
|
+
When wrapped in `AuthProvider`, authentication is automatically synced:
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
import { AuthProvider, SmartlinksFrame } from '@smartlinks/auth-module';
|
|
102
|
+
|
|
103
|
+
<AuthProvider>
|
|
104
|
+
<SmartlinksFrame
|
|
105
|
+
collectionId="acme-wines"
|
|
106
|
+
appId="warranty"
|
|
107
|
+
/>
|
|
108
|
+
</AuthProvider>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Local Development Override
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
<SmartlinksFrame
|
|
115
|
+
collectionId="acme-wines"
|
|
116
|
+
appId="warranty"
|
|
117
|
+
appUrl="http://localhost:5173"
|
|
118
|
+
version="development"
|
|
119
|
+
/>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Deep Linking
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
<SmartlinksFrame
|
|
126
|
+
collectionId="acme-wines"
|
|
127
|
+
appId="warranty"
|
|
128
|
+
initialPath="/products/wine-123"
|
|
129
|
+
onRouteChange={(path, state) => {
|
|
130
|
+
// Update parent URL with iframe route
|
|
131
|
+
const url = new URL(window.location.href);
|
|
132
|
+
url.searchParams.set('app-path', path);
|
|
133
|
+
Object.entries(state).forEach(([key, value]) => {
|
|
134
|
+
url.searchParams.set(key, value);
|
|
135
|
+
});
|
|
136
|
+
window.history.pushState({}, '', url);
|
|
137
|
+
}}
|
|
138
|
+
/>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Responsive Height with Constraints
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
<SmartlinksFrame
|
|
145
|
+
collectionId="acme-wines"
|
|
146
|
+
appId="warranty"
|
|
147
|
+
autoResize={true}
|
|
148
|
+
minHeight={400}
|
|
149
|
+
maxHeight={800}
|
|
150
|
+
/>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Authentication Integration
|
|
154
|
+
|
|
155
|
+
The component automatically integrates with `AuthProvider`:
|
|
156
|
+
|
|
157
|
+
1. **Admin Detection**: Uses `isAdminFromRoles()` to detect admin status from collection/proof roles
|
|
158
|
+
2. **Login Sync**: When iframe triggers login, calls `AuthProvider.login()` to sync session
|
|
159
|
+
3. **Logout Sync**: When iframe triggers logout, calls `AuthProvider.logout()`
|
|
160
|
+
|
|
161
|
+
### Without AuthProvider
|
|
162
|
+
|
|
163
|
+
Works in anonymous mode - API requests still proxied, but no auth sync:
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
// No AuthProvider wrapper - works but no login/logout sync
|
|
167
|
+
<SmartlinksFrame
|
|
168
|
+
collectionId="acme-wines"
|
|
169
|
+
appId="public-app"
|
|
170
|
+
/>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## SDK Helpers
|
|
174
|
+
|
|
175
|
+
The component uses these SDK functions internally (also exported for direct use):
|
|
176
|
+
|
|
177
|
+
### `isAdminFromRoles(user, collection, proof?)`
|
|
178
|
+
|
|
179
|
+
```tsx
|
|
180
|
+
import { isAdminFromRoles } from '@smartlinks/auth-module';
|
|
181
|
+
// or
|
|
182
|
+
import { isAdminFromRoles } from '@proveanything/smartlinks';
|
|
183
|
+
|
|
184
|
+
const isAdmin = isAdminFromRoles(user, collection);
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### `buildIframeSrc(options)`
|
|
188
|
+
|
|
189
|
+
Manually construct iframe URL:
|
|
190
|
+
|
|
191
|
+
```tsx
|
|
192
|
+
import { buildIframeSrc } from '@smartlinks/auth-module';
|
|
193
|
+
|
|
194
|
+
const src = buildIframeSrc({
|
|
195
|
+
appUrl: 'https://warranty.lovable.app',
|
|
196
|
+
collectionId: 'acme-wines',
|
|
197
|
+
appId: 'warranty',
|
|
198
|
+
productId: 'wine-123',
|
|
199
|
+
isAdmin: false,
|
|
200
|
+
dark: true,
|
|
201
|
+
theme: { primary: '#FF6B6B' },
|
|
202
|
+
initialPath: '/register',
|
|
203
|
+
});
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Caching
|
|
207
|
+
|
|
208
|
+
The SDK caches app configuration to avoid redundant API calls:
|
|
209
|
+
|
|
210
|
+
```tsx
|
|
211
|
+
import * as smartlinks from '@proveanything/smartlinks';
|
|
212
|
+
|
|
213
|
+
// Apps cached for 5 minutes in sessionStorage
|
|
214
|
+
const apps = await smartlinks.cache.getOrFetch(
|
|
215
|
+
`apps:${collectionId}`,
|
|
216
|
+
() => smartlinks.collection.getAppsConfig(collectionId),
|
|
217
|
+
{ ttl: 5 * 60 * 1000, storage: 'session' }
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
// Invalidate cache
|
|
221
|
+
smartlinks.cache.invalidate(`apps:${collectionId}`);
|
|
222
|
+
|
|
223
|
+
// Clear all cache
|
|
224
|
+
smartlinks.cache.clear();
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Migration from Direct IframeResponder
|
|
228
|
+
|
|
229
|
+
If you were using `IframeResponder` directly, the React component handles everything:
|
|
230
|
+
|
|
231
|
+
```tsx
|
|
232
|
+
// BEFORE: Manual IframeResponder usage
|
|
233
|
+
const responder = new smartlinks.IframeResponder({
|
|
234
|
+
collectionId: 'acme-wines',
|
|
235
|
+
appId: 'warranty',
|
|
236
|
+
onAuthLogin: async (token, user) => {
|
|
237
|
+
smartlinks.setBearerToken(token);
|
|
238
|
+
},
|
|
239
|
+
onResize: (h) => {
|
|
240
|
+
iframe.style.height = `${h}px`;
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
const src = await responder.attach(iframe);
|
|
244
|
+
|
|
245
|
+
// AFTER: React component handles everything
|
|
246
|
+
<SmartlinksFrame
|
|
247
|
+
collectionId="acme-wines"
|
|
248
|
+
appId="warranty"
|
|
249
|
+
/>
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Backend Requirements
|
|
253
|
+
|
|
254
|
+
The SDK expects this endpoint to be available:
|
|
255
|
+
|
|
256
|
+
```
|
|
257
|
+
GET /public/collection/:collectionId/apps-config
|
|
258
|
+
|
|
259
|
+
Response:
|
|
260
|
+
{
|
|
261
|
+
"apps": [
|
|
262
|
+
{
|
|
263
|
+
"id": "warranty-registration",
|
|
264
|
+
"srcAppId": "warranty-v2",
|
|
265
|
+
"name": "Warranty Registration",
|
|
266
|
+
"publicIframeUrl": "https://warranty.lovable.app",
|
|
267
|
+
"active": true,
|
|
268
|
+
"category": "Commerce",
|
|
269
|
+
"usage": {
|
|
270
|
+
"collection": true,
|
|
271
|
+
"product": true,
|
|
272
|
+
"proof": false,
|
|
273
|
+
"widget": false
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
]
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Each app must have a `publicIframeUrl` configured for URL auto-resolution to work.
|
|
281
|
+
|
|
282
|
+
## Troubleshooting
|
|
283
|
+
|
|
284
|
+
### App not loading
|
|
285
|
+
- Verify the appId exists in collection apps configuration
|
|
286
|
+
- Check browser console for errors
|
|
287
|
+
- Ensure `publicIframeUrl` is set in app config
|
|
288
|
+
|
|
289
|
+
### Authentication not syncing
|
|
290
|
+
- Wrap component in `AuthProvider`
|
|
291
|
+
- Check that auth messages are being received
|
|
292
|
+
- Verify `onAuthLogin` is being called
|
|
293
|
+
|
|
294
|
+
### Height not updating
|
|
295
|
+
- Ensure `autoResize={true}` (default)
|
|
296
|
+
- Check that iframe is sending resize messages
|
|
297
|
+
- Try setting explicit `minHeight`/`maxHeight`
|
|
298
|
+
|
|
299
|
+
### URL not resolving
|
|
300
|
+
- Provide explicit `appUrl` prop as fallback
|
|
301
|
+
- Verify backend `/apps-config` endpoint is available
|
|
302
|
+
- Check network tab for API errors
|